editor_tests.rs

   1use super::*;
   2use crate::{
   3    scroll::scroll_amount::ScrollAmount,
   4    test::{
   5        assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
   6        editor_test_context::EditorTestContext, select_ranges,
   7    },
   8    JoinLines,
   9};
  10use futures::StreamExt;
  11use gpui::{div, TestAppContext, VisualTestContext, WindowOptions};
  12use indoc::indoc;
  13use language::{
  14    language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent},
  15    BracketPairConfig,
  16    Capability::ReadWrite,
  17    FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override, Point,
  18};
  19use parking_lot::Mutex;
  20use project::project_settings::{LspSettings, ProjectSettings};
  21use project::FakeFs;
  22use serde_json::{self, json};
  23use std::sync::atomic;
  24use std::sync::atomic::AtomicUsize;
  25use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
  26use unindent::Unindent;
  27use util::{
  28    assert_set_eq,
  29    test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
  30};
  31use workspace::{
  32    item::{FollowEvent, FollowableItem, Item, ItemHandle},
  33    NavigationEntry, ViewId,
  34};
  35
  36#[gpui::test]
  37fn test_edit_events(cx: &mut TestAppContext) {
  38    init_test(cx, |_| {});
  39
  40    let buffer = cx.new_model(|cx| {
  41        let mut buffer =
  42            language::Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "123456");
  43        buffer.set_group_interval(Duration::from_secs(1));
  44        buffer
  45    });
  46
  47    let events = Rc::new(RefCell::new(Vec::new()));
  48    let editor1 = cx.add_window({
  49        let events = events.clone();
  50        |cx| {
  51            let view = cx.view().clone();
  52            cx.subscribe(&view, move |_, _, event: &EditorEvent, _| {
  53                if matches!(event, EditorEvent::Edited | EditorEvent::BufferEdited) {
  54                    events.borrow_mut().push(("editor1", event.clone()));
  55                }
  56            })
  57            .detach();
  58            Editor::for_buffer(buffer.clone(), None, cx)
  59        }
  60    });
  61
  62    let editor2 = cx.add_window({
  63        let events = events.clone();
  64        |cx| {
  65            cx.subscribe(&cx.view().clone(), move |_, _, event: &EditorEvent, _| {
  66                if matches!(event, EditorEvent::Edited | EditorEvent::BufferEdited) {
  67                    events.borrow_mut().push(("editor2", event.clone()));
  68                }
  69            })
  70            .detach();
  71            Editor::for_buffer(buffer.clone(), None, cx)
  72        }
  73    });
  74
  75    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  76
  77    // Mutating editor 1 will emit an `Edited` event only for that editor.
  78    _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
  79    assert_eq!(
  80        mem::take(&mut *events.borrow_mut()),
  81        [
  82            ("editor1", EditorEvent::Edited),
  83            ("editor1", EditorEvent::BufferEdited),
  84            ("editor2", EditorEvent::BufferEdited),
  85        ]
  86    );
  87
  88    // Mutating editor 2 will emit an `Edited` event only for that editor.
  89    _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
  90    assert_eq!(
  91        mem::take(&mut *events.borrow_mut()),
  92        [
  93            ("editor2", EditorEvent::Edited),
  94            ("editor1", EditorEvent::BufferEdited),
  95            ("editor2", EditorEvent::BufferEdited),
  96        ]
  97    );
  98
  99    // Undoing on editor 1 will emit an `Edited` event only for that editor.
 100    _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
 101    assert_eq!(
 102        mem::take(&mut *events.borrow_mut()),
 103        [
 104            ("editor1", EditorEvent::Edited),
 105            ("editor1", EditorEvent::BufferEdited),
 106            ("editor2", EditorEvent::BufferEdited),
 107        ]
 108    );
 109
 110    // Redoing on editor 1 will emit an `Edited` event only for that editor.
 111    _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
 112    assert_eq!(
 113        mem::take(&mut *events.borrow_mut()),
 114        [
 115            ("editor1", EditorEvent::Edited),
 116            ("editor1", EditorEvent::BufferEdited),
 117            ("editor2", EditorEvent::BufferEdited),
 118        ]
 119    );
 120
 121    // Undoing on editor 2 will emit an `Edited` event only for that editor.
 122    _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
 123    assert_eq!(
 124        mem::take(&mut *events.borrow_mut()),
 125        [
 126            ("editor2", EditorEvent::Edited),
 127            ("editor1", EditorEvent::BufferEdited),
 128            ("editor2", EditorEvent::BufferEdited),
 129        ]
 130    );
 131
 132    // Redoing on editor 2 will emit an `Edited` event only for that editor.
 133    _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
 134    assert_eq!(
 135        mem::take(&mut *events.borrow_mut()),
 136        [
 137            ("editor2", EditorEvent::Edited),
 138            ("editor1", EditorEvent::BufferEdited),
 139            ("editor2", EditorEvent::BufferEdited),
 140        ]
 141    );
 142
 143    // No event is emitted when the mutation is a no-op.
 144    _ = editor2.update(cx, |editor, cx| {
 145        editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
 146
 147        editor.backspace(&Backspace, cx);
 148    });
 149    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
 150}
 151
 152#[gpui::test]
 153fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
 154    init_test(cx, |_| {});
 155
 156    let mut now = Instant::now();
 157    let buffer = cx.new_model(|cx| {
 158        language::Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "123456")
 159    });
 160    let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
 161    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 162    let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
 163
 164    _ = editor.update(cx, |editor, cx| {
 165        editor.start_transaction_at(now, cx);
 166        editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
 167
 168        editor.insert("cd", cx);
 169        editor.end_transaction_at(now, cx);
 170        assert_eq!(editor.text(cx), "12cd56");
 171        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
 172
 173        editor.start_transaction_at(now, cx);
 174        editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
 175        editor.insert("e", cx);
 176        editor.end_transaction_at(now, cx);
 177        assert_eq!(editor.text(cx), "12cde6");
 178        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
 179
 180        now += group_interval + Duration::from_millis(1);
 181        editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
 182
 183        // Simulate an edit in another editor
 184        _ = buffer.update(cx, |buffer, cx| {
 185            buffer.start_transaction_at(now, cx);
 186            buffer.edit([(0..1, "a")], None, cx);
 187            buffer.edit([(1..1, "b")], None, cx);
 188            buffer.end_transaction_at(now, cx);
 189        });
 190
 191        assert_eq!(editor.text(cx), "ab2cde6");
 192        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
 193
 194        // Last transaction happened past the group interval in a different editor.
 195        // Undo it individually and don't restore selections.
 196        editor.undo(&Undo, cx);
 197        assert_eq!(editor.text(cx), "12cde6");
 198        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
 199
 200        // First two transactions happened within the group interval in this editor.
 201        // Undo them together and restore selections.
 202        editor.undo(&Undo, cx);
 203        editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
 204        assert_eq!(editor.text(cx), "123456");
 205        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
 206
 207        // Redo the first two transactions together.
 208        editor.redo(&Redo, cx);
 209        assert_eq!(editor.text(cx), "12cde6");
 210        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
 211
 212        // Redo the last transaction on its own.
 213        editor.redo(&Redo, cx);
 214        assert_eq!(editor.text(cx), "ab2cde6");
 215        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
 216
 217        // Test empty transactions.
 218        editor.start_transaction_at(now, cx);
 219        editor.end_transaction_at(now, cx);
 220        editor.undo(&Undo, cx);
 221        assert_eq!(editor.text(cx), "12cde6");
 222    });
 223}
 224
 225#[gpui::test]
 226fn test_ime_composition(cx: &mut TestAppContext) {
 227    init_test(cx, |_| {});
 228
 229    let buffer = cx.new_model(|cx| {
 230        let mut buffer =
 231            language::Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "abcde");
 232        // Ensure automatic grouping doesn't occur.
 233        buffer.set_group_interval(Duration::ZERO);
 234        buffer
 235    });
 236
 237    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 238    cx.add_window(|cx| {
 239        let mut editor = build_editor(buffer.clone(), cx);
 240
 241        // Start a new IME composition.
 242        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
 243        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
 244        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
 245        assert_eq!(editor.text(cx), "äbcde");
 246        assert_eq!(
 247            editor.marked_text_ranges(cx),
 248            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
 249        );
 250
 251        // Finalize IME composition.
 252        editor.replace_text_in_range(None, "ā", cx);
 253        assert_eq!(editor.text(cx), "ābcde");
 254        assert_eq!(editor.marked_text_ranges(cx), None);
 255
 256        // IME composition edits are grouped and are undone/redone at once.
 257        editor.undo(&Default::default(), cx);
 258        assert_eq!(editor.text(cx), "abcde");
 259        assert_eq!(editor.marked_text_ranges(cx), None);
 260        editor.redo(&Default::default(), cx);
 261        assert_eq!(editor.text(cx), "ābcde");
 262        assert_eq!(editor.marked_text_ranges(cx), None);
 263
 264        // Start a new IME composition.
 265        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
 266        assert_eq!(
 267            editor.marked_text_ranges(cx),
 268            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
 269        );
 270
 271        // Undoing during an IME composition cancels it.
 272        editor.undo(&Default::default(), cx);
 273        assert_eq!(editor.text(cx), "ābcde");
 274        assert_eq!(editor.marked_text_ranges(cx), None);
 275
 276        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
 277        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
 278        assert_eq!(editor.text(cx), "ābcdè");
 279        assert_eq!(
 280            editor.marked_text_ranges(cx),
 281            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
 282        );
 283
 284        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
 285        editor.replace_text_in_range(Some(4..999), "ę", cx);
 286        assert_eq!(editor.text(cx), "ābcdę");
 287        assert_eq!(editor.marked_text_ranges(cx), None);
 288
 289        // Start a new IME composition with multiple cursors.
 290        editor.change_selections(None, cx, |s| {
 291            s.select_ranges([
 292                OffsetUtf16(1)..OffsetUtf16(1),
 293                OffsetUtf16(3)..OffsetUtf16(3),
 294                OffsetUtf16(5)..OffsetUtf16(5),
 295            ])
 296        });
 297        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
 298        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
 299        assert_eq!(
 300            editor.marked_text_ranges(cx),
 301            Some(vec![
 302                OffsetUtf16(0)..OffsetUtf16(3),
 303                OffsetUtf16(4)..OffsetUtf16(7),
 304                OffsetUtf16(8)..OffsetUtf16(11)
 305            ])
 306        );
 307
 308        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
 309        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
 310        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
 311        assert_eq!(
 312            editor.marked_text_ranges(cx),
 313            Some(vec![
 314                OffsetUtf16(1)..OffsetUtf16(2),
 315                OffsetUtf16(5)..OffsetUtf16(6),
 316                OffsetUtf16(9)..OffsetUtf16(10)
 317            ])
 318        );
 319
 320        // Finalize IME composition with multiple cursors.
 321        editor.replace_text_in_range(Some(9..10), "2", cx);
 322        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
 323        assert_eq!(editor.marked_text_ranges(cx), None);
 324
 325        editor
 326    });
 327}
 328
 329#[gpui::test]
 330fn test_selection_with_mouse(cx: &mut TestAppContext) {
 331    init_test(cx, |_| {});
 332
 333    let editor = cx.add_window(|cx| {
 334        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
 335        build_editor(buffer, cx)
 336    });
 337
 338    _ = editor.update(cx, |view, cx| {
 339        view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
 340    });
 341    assert_eq!(
 342        editor
 343            .update(cx, |view, cx| view.selections.display_ranges(cx))
 344            .unwrap(),
 345        [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
 346    );
 347
 348    _ = editor.update(cx, |view, cx| {
 349        view.update_selection(
 350            DisplayPoint::new(3, 3),
 351            0,
 352            gpui::Point::<f32>::default(),
 353            cx,
 354        );
 355    });
 356
 357    assert_eq!(
 358        editor
 359            .update(cx, |view, cx| view.selections.display_ranges(cx))
 360            .unwrap(),
 361        [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
 362    );
 363
 364    _ = editor.update(cx, |view, cx| {
 365        view.update_selection(
 366            DisplayPoint::new(1, 1),
 367            0,
 368            gpui::Point::<f32>::default(),
 369            cx,
 370        );
 371    });
 372
 373    assert_eq!(
 374        editor
 375            .update(cx, |view, cx| view.selections.display_ranges(cx))
 376            .unwrap(),
 377        [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
 378    );
 379
 380    _ = editor.update(cx, |view, cx| {
 381        view.end_selection(cx);
 382        view.update_selection(
 383            DisplayPoint::new(3, 3),
 384            0,
 385            gpui::Point::<f32>::default(),
 386            cx,
 387        );
 388    });
 389
 390    assert_eq!(
 391        editor
 392            .update(cx, |view, cx| view.selections.display_ranges(cx))
 393            .unwrap(),
 394        [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
 395    );
 396
 397    _ = editor.update(cx, |view, cx| {
 398        view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx);
 399        view.update_selection(
 400            DisplayPoint::new(0, 0),
 401            0,
 402            gpui::Point::<f32>::default(),
 403            cx,
 404        );
 405    });
 406
 407    assert_eq!(
 408        editor
 409            .update(cx, |view, cx| view.selections.display_ranges(cx))
 410            .unwrap(),
 411        [
 412            DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
 413            DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
 414        ]
 415    );
 416
 417    _ = editor.update(cx, |view, cx| {
 418        view.end_selection(cx);
 419    });
 420
 421    assert_eq!(
 422        editor
 423            .update(cx, |view, cx| view.selections.display_ranges(cx))
 424            .unwrap(),
 425        [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
 426    );
 427}
 428
 429#[gpui::test]
 430fn test_canceling_pending_selection(cx: &mut TestAppContext) {
 431    init_test(cx, |_| {});
 432
 433    let view = cx.add_window(|cx| {
 434        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
 435        build_editor(buffer, cx)
 436    });
 437
 438    _ = view.update(cx, |view, cx| {
 439        view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
 440        assert_eq!(
 441            view.selections.display_ranges(cx),
 442            [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
 443        );
 444    });
 445
 446    _ = view.update(cx, |view, cx| {
 447        view.update_selection(
 448            DisplayPoint::new(3, 3),
 449            0,
 450            gpui::Point::<f32>::default(),
 451            cx,
 452        );
 453        assert_eq!(
 454            view.selections.display_ranges(cx),
 455            [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
 456        );
 457    });
 458
 459    _ = view.update(cx, |view, cx| {
 460        view.cancel(&Cancel, cx);
 461        view.update_selection(
 462            DisplayPoint::new(1, 1),
 463            0,
 464            gpui::Point::<f32>::default(),
 465            cx,
 466        );
 467        assert_eq!(
 468            view.selections.display_ranges(cx),
 469            [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
 470        );
 471    });
 472}
 473
 474#[gpui::test]
 475fn test_clone(cx: &mut TestAppContext) {
 476    init_test(cx, |_| {});
 477
 478    let (text, selection_ranges) = marked_text_ranges(
 479        indoc! {"
 480            one
 481            two
 482            threeˇ
 483            four
 484            fiveˇ
 485        "},
 486        true,
 487    );
 488
 489    let editor = cx.add_window(|cx| {
 490        let buffer = MultiBuffer::build_simple(&text, cx);
 491        build_editor(buffer, cx)
 492    });
 493
 494    _ = editor.update(cx, |editor, cx| {
 495        editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
 496        editor.fold_ranges(
 497            [
 498                Point::new(1, 0)..Point::new(2, 0),
 499                Point::new(3, 0)..Point::new(4, 0),
 500            ],
 501            true,
 502            cx,
 503        );
 504    });
 505
 506    let cloned_editor = editor
 507        .update(cx, |editor, cx| {
 508            cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
 509        })
 510        .unwrap();
 511
 512    let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
 513    let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
 514
 515    assert_eq!(
 516        cloned_editor
 517            .update(cx, |e, cx| e.display_text(cx))
 518            .unwrap(),
 519        editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
 520    );
 521    assert_eq!(
 522        cloned_snapshot
 523            .folds_in_range(0..text.len())
 524            .collect::<Vec<_>>(),
 525        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
 526    );
 527    assert_set_eq!(
 528        cloned_editor
 529            .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
 530            .unwrap(),
 531        editor
 532            .update(cx, |editor, cx| editor.selections.ranges(cx))
 533            .unwrap()
 534    );
 535    assert_set_eq!(
 536        cloned_editor
 537            .update(cx, |e, cx| e.selections.display_ranges(cx))
 538            .unwrap(),
 539        editor
 540            .update(cx, |e, cx| e.selections.display_ranges(cx))
 541            .unwrap()
 542    );
 543}
 544
 545#[gpui::test]
 546async fn test_navigation_history(cx: &mut TestAppContext) {
 547    init_test(cx, |_| {});
 548
 549    use workspace::item::Item;
 550
 551    let fs = FakeFs::new(cx.executor());
 552    let project = Project::test(fs, [], cx).await;
 553    let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
 554    let pane = workspace
 555        .update(cx, |workspace, _| workspace.active_pane().clone())
 556        .unwrap();
 557
 558    _ = workspace.update(cx, |_v, cx| {
 559        cx.new_view(|cx| {
 560            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
 561            let mut editor = build_editor(buffer.clone(), cx);
 562            let handle = cx.view();
 563            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
 564
 565            fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
 566                editor.nav_history.as_mut().unwrap().pop_backward(cx)
 567            }
 568
 569            // Move the cursor a small distance.
 570            // Nothing is added to the navigation history.
 571            editor.change_selections(None, cx, |s| {
 572                s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
 573            });
 574            editor.change_selections(None, cx, |s| {
 575                s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
 576            });
 577            assert!(pop_history(&mut editor, cx).is_none());
 578
 579            // Move the cursor a large distance.
 580            // The history can jump back to the previous position.
 581            editor.change_selections(None, cx, |s| {
 582                s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)])
 583            });
 584            let nav_entry = pop_history(&mut editor, cx).unwrap();
 585            editor.navigate(nav_entry.data.unwrap(), cx);
 586            assert_eq!(nav_entry.item.id(), cx.entity_id());
 587            assert_eq!(
 588                editor.selections.display_ranges(cx),
 589                &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
 590            );
 591            assert!(pop_history(&mut editor, cx).is_none());
 592
 593            // Move the cursor a small distance via the mouse.
 594            // Nothing is added to the navigation history.
 595            editor.begin_selection(DisplayPoint::new(5, 0), false, 1, cx);
 596            editor.end_selection(cx);
 597            assert_eq!(
 598                editor.selections.display_ranges(cx),
 599                &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
 600            );
 601            assert!(pop_history(&mut editor, cx).is_none());
 602
 603            // Move the cursor a large distance via the mouse.
 604            // The history can jump back to the previous position.
 605            editor.begin_selection(DisplayPoint::new(15, 0), false, 1, cx);
 606            editor.end_selection(cx);
 607            assert_eq!(
 608                editor.selections.display_ranges(cx),
 609                &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)]
 610            );
 611            let nav_entry = pop_history(&mut editor, cx).unwrap();
 612            editor.navigate(nav_entry.data.unwrap(), cx);
 613            assert_eq!(nav_entry.item.id(), cx.entity_id());
 614            assert_eq!(
 615                editor.selections.display_ranges(cx),
 616                &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
 617            );
 618            assert!(pop_history(&mut editor, cx).is_none());
 619
 620            // Set scroll position to check later
 621            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
 622            let original_scroll_position = editor.scroll_manager.anchor();
 623
 624            // Jump to the end of the document and adjust scroll
 625            editor.move_to_end(&MoveToEnd, cx);
 626            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
 627            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
 628
 629            let nav_entry = pop_history(&mut editor, cx).unwrap();
 630            editor.navigate(nav_entry.data.unwrap(), cx);
 631            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
 632
 633            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
 634            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
 635            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
 636            let invalid_point = Point::new(9999, 0);
 637            editor.navigate(
 638                Box::new(NavigationData {
 639                    cursor_anchor: invalid_anchor,
 640                    cursor_position: invalid_point,
 641                    scroll_anchor: ScrollAnchor {
 642                        anchor: invalid_anchor,
 643                        offset: Default::default(),
 644                    },
 645                    scroll_top_row: invalid_point.row,
 646                }),
 647                cx,
 648            );
 649            assert_eq!(
 650                editor.selections.display_ranges(cx),
 651                &[editor.max_point(cx)..editor.max_point(cx)]
 652            );
 653            assert_eq!(
 654                editor.scroll_position(cx),
 655                gpui::Point::new(0., editor.max_point(cx).row() as f32)
 656            );
 657
 658            editor
 659        })
 660    });
 661}
 662
 663#[gpui::test]
 664fn test_cancel(cx: &mut TestAppContext) {
 665    init_test(cx, |_| {});
 666
 667    let view = cx.add_window(|cx| {
 668        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
 669        build_editor(buffer, cx)
 670    });
 671
 672    _ = view.update(cx, |view, cx| {
 673        view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
 674        view.update_selection(
 675            DisplayPoint::new(1, 1),
 676            0,
 677            gpui::Point::<f32>::default(),
 678            cx,
 679        );
 680        view.end_selection(cx);
 681
 682        view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx);
 683        view.update_selection(
 684            DisplayPoint::new(0, 3),
 685            0,
 686            gpui::Point::<f32>::default(),
 687            cx,
 688        );
 689        view.end_selection(cx);
 690        assert_eq!(
 691            view.selections.display_ranges(cx),
 692            [
 693                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
 694                DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1),
 695            ]
 696        );
 697    });
 698
 699    _ = view.update(cx, |view, cx| {
 700        view.cancel(&Cancel, cx);
 701        assert_eq!(
 702            view.selections.display_ranges(cx),
 703            [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)]
 704        );
 705    });
 706
 707    _ = view.update(cx, |view, cx| {
 708        view.cancel(&Cancel, cx);
 709        assert_eq!(
 710            view.selections.display_ranges(cx),
 711            [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
 712        );
 713    });
 714}
 715
 716#[gpui::test]
 717fn test_fold_action(cx: &mut TestAppContext) {
 718    init_test(cx, |_| {});
 719
 720    let view = cx.add_window(|cx| {
 721        let buffer = MultiBuffer::build_simple(
 722            &"
 723                impl Foo {
 724                    // Hello!
 725
 726                    fn a() {
 727                        1
 728                    }
 729
 730                    fn b() {
 731                        2
 732                    }
 733
 734                    fn c() {
 735                        3
 736                    }
 737                }
 738            "
 739            .unindent(),
 740            cx,
 741        );
 742        build_editor(buffer.clone(), cx)
 743    });
 744
 745    _ = view.update(cx, |view, cx| {
 746        view.change_selections(None, cx, |s| {
 747            s.select_display_ranges([DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)]);
 748        });
 749        view.fold(&Fold, cx);
 750        assert_eq!(
 751            view.display_text(cx),
 752            "
 753                impl Foo {
 754                    // Hello!
 755
 756                    fn a() {
 757                        1
 758                    }
 759
 760                    fn b() {⋯
 761                    }
 762
 763                    fn c() {⋯
 764                    }
 765                }
 766            "
 767            .unindent(),
 768        );
 769
 770        view.fold(&Fold, cx);
 771        assert_eq!(
 772            view.display_text(cx),
 773            "
 774                impl Foo {⋯
 775                }
 776            "
 777            .unindent(),
 778        );
 779
 780        view.unfold_lines(&UnfoldLines, cx);
 781        assert_eq!(
 782            view.display_text(cx),
 783            "
 784                impl Foo {
 785                    // Hello!
 786
 787                    fn a() {
 788                        1
 789                    }
 790
 791                    fn b() {⋯
 792                    }
 793
 794                    fn c() {⋯
 795                    }
 796                }
 797            "
 798            .unindent(),
 799        );
 800
 801        view.unfold_lines(&UnfoldLines, cx);
 802        assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
 803    });
 804}
 805
 806#[gpui::test]
 807fn test_move_cursor(cx: &mut TestAppContext) {
 808    init_test(cx, |_| {});
 809
 810    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 811    let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
 812
 813    _ = buffer.update(cx, |buffer, cx| {
 814        buffer.edit(
 815            vec![
 816                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 817                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 818            ],
 819            None,
 820            cx,
 821        );
 822    });
 823    _ = view.update(cx, |view, cx| {
 824        assert_eq!(
 825            view.selections.display_ranges(cx),
 826            &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
 827        );
 828
 829        view.move_down(&MoveDown, cx);
 830        assert_eq!(
 831            view.selections.display_ranges(cx),
 832            &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
 833        );
 834
 835        view.move_right(&MoveRight, cx);
 836        assert_eq!(
 837            view.selections.display_ranges(cx),
 838            &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)]
 839        );
 840
 841        view.move_left(&MoveLeft, cx);
 842        assert_eq!(
 843            view.selections.display_ranges(cx),
 844            &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
 845        );
 846
 847        view.move_up(&MoveUp, cx);
 848        assert_eq!(
 849            view.selections.display_ranges(cx),
 850            &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
 851        );
 852
 853        view.move_to_end(&MoveToEnd, cx);
 854        assert_eq!(
 855            view.selections.display_ranges(cx),
 856            &[DisplayPoint::new(5, 6)..DisplayPoint::new(5, 6)]
 857        );
 858
 859        view.move_to_beginning(&MoveToBeginning, cx);
 860        assert_eq!(
 861            view.selections.display_ranges(cx),
 862            &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
 863        );
 864
 865        view.change_selections(None, cx, |s| {
 866            s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)]);
 867        });
 868        view.select_to_beginning(&SelectToBeginning, cx);
 869        assert_eq!(
 870            view.selections.display_ranges(cx),
 871            &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 0)]
 872        );
 873
 874        view.select_to_end(&SelectToEnd, cx);
 875        assert_eq!(
 876            view.selections.display_ranges(cx),
 877            &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)]
 878        );
 879    });
 880}
 881
 882#[gpui::test]
 883fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 884    init_test(cx, |_| {});
 885
 886    let view = cx.add_window(|cx| {
 887        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
 888        build_editor(buffer.clone(), cx)
 889    });
 890
 891    assert_eq!('ⓐ'.len_utf8(), 3);
 892    assert_eq!('α'.len_utf8(), 2);
 893
 894    _ = view.update(cx, |view, cx| {
 895        view.fold_ranges(
 896            vec![
 897                Point::new(0, 6)..Point::new(0, 12),
 898                Point::new(1, 2)..Point::new(1, 4),
 899                Point::new(2, 4)..Point::new(2, 8),
 900            ],
 901            true,
 902            cx,
 903        );
 904        assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
 905
 906        view.move_right(&MoveRight, cx);
 907        assert_eq!(
 908            view.selections.display_ranges(cx),
 909            &[empty_range(0, "".len())]
 910        );
 911        view.move_right(&MoveRight, cx);
 912        assert_eq!(
 913            view.selections.display_ranges(cx),
 914            &[empty_range(0, "ⓐⓑ".len())]
 915        );
 916        view.move_right(&MoveRight, cx);
 917        assert_eq!(
 918            view.selections.display_ranges(cx),
 919            &[empty_range(0, "ⓐⓑ⋯".len())]
 920        );
 921
 922        view.move_down(&MoveDown, cx);
 923        assert_eq!(
 924            view.selections.display_ranges(cx),
 925            &[empty_range(1, "ab⋯e".len())]
 926        );
 927        view.move_left(&MoveLeft, cx);
 928        assert_eq!(
 929            view.selections.display_ranges(cx),
 930            &[empty_range(1, "ab⋯".len())]
 931        );
 932        view.move_left(&MoveLeft, cx);
 933        assert_eq!(
 934            view.selections.display_ranges(cx),
 935            &[empty_range(1, "ab".len())]
 936        );
 937        view.move_left(&MoveLeft, cx);
 938        assert_eq!(
 939            view.selections.display_ranges(cx),
 940            &[empty_range(1, "a".len())]
 941        );
 942
 943        view.move_down(&MoveDown, cx);
 944        assert_eq!(
 945            view.selections.display_ranges(cx),
 946            &[empty_range(2, "α".len())]
 947        );
 948        view.move_right(&MoveRight, cx);
 949        assert_eq!(
 950            view.selections.display_ranges(cx),
 951            &[empty_range(2, "αβ".len())]
 952        );
 953        view.move_right(&MoveRight, cx);
 954        assert_eq!(
 955            view.selections.display_ranges(cx),
 956            &[empty_range(2, "αβ⋯".len())]
 957        );
 958        view.move_right(&MoveRight, cx);
 959        assert_eq!(
 960            view.selections.display_ranges(cx),
 961            &[empty_range(2, "αβ⋯ε".len())]
 962        );
 963
 964        view.move_up(&MoveUp, cx);
 965        assert_eq!(
 966            view.selections.display_ranges(cx),
 967            &[empty_range(1, "ab⋯e".len())]
 968        );
 969        view.move_down(&MoveDown, cx);
 970        assert_eq!(
 971            view.selections.display_ranges(cx),
 972            &[empty_range(2, "αβ⋯ε".len())]
 973        );
 974        view.move_up(&MoveUp, cx);
 975        assert_eq!(
 976            view.selections.display_ranges(cx),
 977            &[empty_range(1, "ab⋯e".len())]
 978        );
 979
 980        view.move_up(&MoveUp, cx);
 981        assert_eq!(
 982            view.selections.display_ranges(cx),
 983            &[empty_range(0, "ⓐⓑ".len())]
 984        );
 985        view.move_left(&MoveLeft, cx);
 986        assert_eq!(
 987            view.selections.display_ranges(cx),
 988            &[empty_range(0, "".len())]
 989        );
 990        view.move_left(&MoveLeft, cx);
 991        assert_eq!(
 992            view.selections.display_ranges(cx),
 993            &[empty_range(0, "".len())]
 994        );
 995    });
 996}
 997
 998#[gpui::test]
 999fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1000    init_test(cx, |_| {});
1001
1002    let view = cx.add_window(|cx| {
1003        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1004        build_editor(buffer.clone(), cx)
1005    });
1006    _ = view.update(cx, |view, cx| {
1007        view.change_selections(None, cx, |s| {
1008            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1009        });
1010        view.move_down(&MoveDown, cx);
1011        assert_eq!(
1012            view.selections.display_ranges(cx),
1013            &[empty_range(1, "abcd".len())]
1014        );
1015
1016        view.move_down(&MoveDown, cx);
1017        assert_eq!(
1018            view.selections.display_ranges(cx),
1019            &[empty_range(2, "αβγ".len())]
1020        );
1021
1022        view.move_down(&MoveDown, cx);
1023        assert_eq!(
1024            view.selections.display_ranges(cx),
1025            &[empty_range(3, "abcd".len())]
1026        );
1027
1028        view.move_down(&MoveDown, cx);
1029        assert_eq!(
1030            view.selections.display_ranges(cx),
1031            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1032        );
1033
1034        view.move_up(&MoveUp, cx);
1035        assert_eq!(
1036            view.selections.display_ranges(cx),
1037            &[empty_range(3, "abcd".len())]
1038        );
1039
1040        view.move_up(&MoveUp, cx);
1041        assert_eq!(
1042            view.selections.display_ranges(cx),
1043            &[empty_range(2, "αβγ".len())]
1044        );
1045    });
1046}
1047
1048#[gpui::test]
1049fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1050    init_test(cx, |_| {});
1051
1052    let view = cx.add_window(|cx| {
1053        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
1054        build_editor(buffer, cx)
1055    });
1056    _ = view.update(cx, |view, cx| {
1057        view.change_selections(None, cx, |s| {
1058            s.select_display_ranges([
1059                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
1060                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
1061            ]);
1062        });
1063    });
1064
1065    _ = view.update(cx, |view, cx| {
1066        view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
1067        assert_eq!(
1068            view.selections.display_ranges(cx),
1069            &[
1070                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1071                DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
1072            ]
1073        );
1074    });
1075
1076    _ = view.update(cx, |view, cx| {
1077        view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
1078        assert_eq!(
1079            view.selections.display_ranges(cx),
1080            &[
1081                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1082                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
1083            ]
1084        );
1085    });
1086
1087    _ = view.update(cx, |view, cx| {
1088        view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
1089        assert_eq!(
1090            view.selections.display_ranges(cx),
1091            &[
1092                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1093                DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
1094            ]
1095        );
1096    });
1097
1098    _ = view.update(cx, |view, cx| {
1099        view.move_to_end_of_line(&MoveToEndOfLine, cx);
1100        assert_eq!(
1101            view.selections.display_ranges(cx),
1102            &[
1103                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1104                DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
1105            ]
1106        );
1107    });
1108
1109    // Moving to the end of line again is a no-op.
1110    _ = view.update(cx, |view, cx| {
1111        view.move_to_end_of_line(&MoveToEndOfLine, cx);
1112        assert_eq!(
1113            view.selections.display_ranges(cx),
1114            &[
1115                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1116                DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
1117            ]
1118        );
1119    });
1120
1121    _ = view.update(cx, |view, cx| {
1122        view.move_left(&MoveLeft, cx);
1123        view.select_to_beginning_of_line(
1124            &SelectToBeginningOfLine {
1125                stop_at_soft_wraps: true,
1126            },
1127            cx,
1128        );
1129        assert_eq!(
1130            view.selections.display_ranges(cx),
1131            &[
1132                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1133                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
1134            ]
1135        );
1136    });
1137
1138    _ = view.update(cx, |view, cx| {
1139        view.select_to_beginning_of_line(
1140            &SelectToBeginningOfLine {
1141                stop_at_soft_wraps: true,
1142            },
1143            cx,
1144        );
1145        assert_eq!(
1146            view.selections.display_ranges(cx),
1147            &[
1148                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1149                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 0),
1150            ]
1151        );
1152    });
1153
1154    _ = view.update(cx, |view, cx| {
1155        view.select_to_beginning_of_line(
1156            &SelectToBeginningOfLine {
1157                stop_at_soft_wraps: true,
1158            },
1159            cx,
1160        );
1161        assert_eq!(
1162            view.selections.display_ranges(cx),
1163            &[
1164                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1165                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
1166            ]
1167        );
1168    });
1169
1170    _ = view.update(cx, |view, cx| {
1171        view.select_to_end_of_line(
1172            &SelectToEndOfLine {
1173                stop_at_soft_wraps: true,
1174            },
1175            cx,
1176        );
1177        assert_eq!(
1178            view.selections.display_ranges(cx),
1179            &[
1180                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
1181                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 5),
1182            ]
1183        );
1184    });
1185
1186    _ = view.update(cx, |view, cx| {
1187        view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1188        assert_eq!(view.display_text(cx), "ab\n  de");
1189        assert_eq!(
1190            view.selections.display_ranges(cx),
1191            &[
1192                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1193                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
1194            ]
1195        );
1196    });
1197
1198    _ = view.update(cx, |view, cx| {
1199        view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1200        assert_eq!(view.display_text(cx), "\n");
1201        assert_eq!(
1202            view.selections.display_ranges(cx),
1203            &[
1204                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1205                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
1206            ]
1207        );
1208    });
1209}
1210
1211#[gpui::test]
1212fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1213    init_test(cx, |_| {});
1214
1215    let view = cx.add_window(|cx| {
1216        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
1217        build_editor(buffer, cx)
1218    });
1219    _ = view.update(cx, |view, cx| {
1220        view.change_selections(None, cx, |s| {
1221            s.select_display_ranges([
1222                DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11),
1223                DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4),
1224            ])
1225        });
1226
1227        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1228        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", view, cx);
1229
1230        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1231        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n  ˇ{baz.qux()}", view, cx);
1232
1233        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1234        assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ  {baz.qux()}", view, cx);
1235
1236        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1237        assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n  {baz.qux()}", view, cx);
1238
1239        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1240        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", view, cx);
1241
1242        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1243        assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n  {baz.qux()}", view, cx);
1244
1245        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1246        assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n  {baz.qux()}", view, cx);
1247
1248        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1249        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", view, cx);
1250
1251        view.move_right(&MoveRight, cx);
1252        view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1253        assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}", view, cx);
1254
1255        view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1256        assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n  «ˇ{b»az.qux()}", view, cx);
1257
1258        view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1259        assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}", view, cx);
1260    });
1261}
1262
1263#[gpui::test]
1264fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1265    init_test(cx, |_| {});
1266
1267    let view = cx.add_window(|cx| {
1268        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
1269        build_editor(buffer, cx)
1270    });
1271
1272    _ = view.update(cx, |view, cx| {
1273        view.set_wrap_width(Some(140.0.into()), cx);
1274        assert_eq!(
1275            view.display_text(cx),
1276            "use one::{\n    two::three::\n    four::five\n};"
1277        );
1278
1279        view.change_selections(None, cx, |s| {
1280            s.select_display_ranges([DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)]);
1281        });
1282
1283        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1284        assert_eq!(
1285            view.selections.display_ranges(cx),
1286            &[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)]
1287        );
1288
1289        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1290        assert_eq!(
1291            view.selections.display_ranges(cx),
1292            &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
1293        );
1294
1295        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1296        assert_eq!(
1297            view.selections.display_ranges(cx),
1298            &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
1299        );
1300
1301        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1302        assert_eq!(
1303            view.selections.display_ranges(cx),
1304            &[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)]
1305        );
1306
1307        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1308        assert_eq!(
1309            view.selections.display_ranges(cx),
1310            &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
1311        );
1312
1313        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1314        assert_eq!(
1315            view.selections.display_ranges(cx),
1316            &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
1317        );
1318    });
1319}
1320
1321#[gpui::test]
1322async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1323    init_test(cx, |_| {});
1324    let mut cx = EditorTestContext::new(cx).await;
1325
1326    let line_height = cx.editor(|editor, cx| {
1327        editor
1328            .style()
1329            .unwrap()
1330            .text
1331            .line_height_in_pixels(cx.rem_size())
1332    });
1333    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1334
1335    cx.set_state(
1336        &r#"ˇone
1337        two
1338
1339        three
1340        fourˇ
1341        five
1342
1343        six"#
1344            .unindent(),
1345    );
1346
1347    cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1348    cx.assert_editor_state(
1349        &r#"one
1350        two
1351        ˇ
1352        three
1353        four
1354        five
1355        ˇ
1356        six"#
1357            .unindent(),
1358    );
1359
1360    cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1361    cx.assert_editor_state(
1362        &r#"one
1363        two
1364
1365        three
1366        four
1367        five
1368        ˇ
1369        sixˇ"#
1370            .unindent(),
1371    );
1372
1373    cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1374    cx.assert_editor_state(
1375        &r#"one
1376        two
1377
1378        three
1379        four
1380        five
1381
1382        sixˇ"#
1383            .unindent(),
1384    );
1385
1386    cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1387    cx.assert_editor_state(
1388        &r#"one
1389        two
1390
1391        three
1392        four
1393        five
1394        ˇ
1395        six"#
1396            .unindent(),
1397    );
1398
1399    cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1400    cx.assert_editor_state(
1401        &r#"one
1402        two
1403        ˇ
1404        three
1405        four
1406        five
1407
1408        six"#
1409            .unindent(),
1410    );
1411
1412    cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1413    cx.assert_editor_state(
1414        &r#"ˇone
1415        two
1416
1417        three
1418        four
1419        five
1420
1421        six"#
1422            .unindent(),
1423    );
1424}
1425
1426#[gpui::test]
1427async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1428    init_test(cx, |_| {});
1429    let mut cx = EditorTestContext::new(cx).await;
1430    let line_height = cx.editor(|editor, cx| {
1431        editor
1432            .style()
1433            .unwrap()
1434            .text
1435            .line_height_in_pixels(cx.rem_size())
1436    });
1437    let window = cx.window;
1438    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1439
1440    cx.set_state(
1441        &r#"ˇone
1442        two
1443        three
1444        four
1445        five
1446        six
1447        seven
1448        eight
1449        nine
1450        ten
1451        "#,
1452    );
1453
1454    cx.update_editor(|editor, cx| {
1455        assert_eq!(
1456            editor.snapshot(cx).scroll_position(),
1457            gpui::Point::new(0., 0.)
1458        );
1459        editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1460        assert_eq!(
1461            editor.snapshot(cx).scroll_position(),
1462            gpui::Point::new(0., 3.)
1463        );
1464        editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1465        assert_eq!(
1466            editor.snapshot(cx).scroll_position(),
1467            gpui::Point::new(0., 6.)
1468        );
1469        editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1470        assert_eq!(
1471            editor.snapshot(cx).scroll_position(),
1472            gpui::Point::new(0., 3.)
1473        );
1474
1475        editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1476        assert_eq!(
1477            editor.snapshot(cx).scroll_position(),
1478            gpui::Point::new(0., 1.)
1479        );
1480        editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1481        assert_eq!(
1482            editor.snapshot(cx).scroll_position(),
1483            gpui::Point::new(0., 3.)
1484        );
1485    });
1486}
1487
1488#[gpui::test]
1489async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1490    init_test(cx, |_| {});
1491    let mut cx = EditorTestContext::new(cx).await;
1492
1493    let line_height = cx.update_editor(|editor, cx| {
1494        editor.set_vertical_scroll_margin(2, cx);
1495        editor
1496            .style()
1497            .unwrap()
1498            .text
1499            .line_height_in_pixels(cx.rem_size())
1500    });
1501    let window = cx.window;
1502    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
1503
1504    cx.set_state(
1505        &r#"ˇone
1506            two
1507            three
1508            four
1509            five
1510            six
1511            seven
1512            eight
1513            nine
1514            ten
1515        "#,
1516    );
1517    cx.update_editor(|editor, cx| {
1518        assert_eq!(
1519            editor.snapshot(cx).scroll_position(),
1520            gpui::Point::new(0., 0.0)
1521        );
1522    });
1523
1524    // Add a cursor below the visible area. Since both cursors cannot fit
1525    // on screen, the editor autoscrolls to reveal the newest cursor, and
1526    // allows the vertical scroll margin below that cursor.
1527    cx.update_editor(|editor, cx| {
1528        editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1529            selections.select_ranges([
1530                Point::new(0, 0)..Point::new(0, 0),
1531                Point::new(6, 0)..Point::new(6, 0),
1532            ]);
1533        })
1534    });
1535    cx.update_editor(|editor, cx| {
1536        assert_eq!(
1537            editor.snapshot(cx).scroll_position(),
1538            gpui::Point::new(0., 3.0)
1539        );
1540    });
1541
1542    // Move down. The editor cursor scrolls down to track the newest cursor.
1543    cx.update_editor(|editor, cx| {
1544        editor.move_down(&Default::default(), cx);
1545    });
1546    cx.update_editor(|editor, cx| {
1547        assert_eq!(
1548            editor.snapshot(cx).scroll_position(),
1549            gpui::Point::new(0., 4.0)
1550        );
1551    });
1552
1553    // Add a cursor above the visible area. Since both cursors fit on screen,
1554    // the editor scrolls to show both.
1555    cx.update_editor(|editor, cx| {
1556        editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1557            selections.select_ranges([
1558                Point::new(1, 0)..Point::new(1, 0),
1559                Point::new(6, 0)..Point::new(6, 0),
1560            ]);
1561        })
1562    });
1563    cx.update_editor(|editor, cx| {
1564        assert_eq!(
1565            editor.snapshot(cx).scroll_position(),
1566            gpui::Point::new(0., 1.0)
1567        );
1568    });
1569}
1570
1571#[gpui::test]
1572async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
1573    init_test(cx, |_| {});
1574    let mut cx = EditorTestContext::new(cx).await;
1575
1576    let line_height = cx.editor(|editor, cx| {
1577        editor
1578            .style()
1579            .unwrap()
1580            .text
1581            .line_height_in_pixels(cx.rem_size())
1582    });
1583    let window = cx.window;
1584    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
1585    cx.set_state(
1586        &r#"
1587        ˇone
1588        two
1589        threeˇ
1590        four
1591        five
1592        six
1593        seven
1594        eight
1595        nine
1596        ten
1597        "#
1598        .unindent(),
1599    );
1600
1601    cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1602    cx.assert_editor_state(
1603        &r#"
1604        one
1605        two
1606        three
1607        ˇfour
1608        five
1609        sixˇ
1610        seven
1611        eight
1612        nine
1613        ten
1614        "#
1615        .unindent(),
1616    );
1617
1618    cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1619    cx.assert_editor_state(
1620        &r#"
1621        one
1622        two
1623        three
1624        four
1625        five
1626        six
1627        ˇseven
1628        eight
1629        nineˇ
1630        ten
1631        "#
1632        .unindent(),
1633    );
1634
1635    cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
1636    cx.assert_editor_state(
1637        &r#"
1638        one
1639        two
1640        three
1641        ˇfour
1642        five
1643        sixˇ
1644        seven
1645        eight
1646        nine
1647        ten
1648        "#
1649        .unindent(),
1650    );
1651
1652    cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
1653    cx.assert_editor_state(
1654        &r#"
1655        ˇone
1656        two
1657        threeˇ
1658        four
1659        five
1660        six
1661        seven
1662        eight
1663        nine
1664        ten
1665        "#
1666        .unindent(),
1667    );
1668
1669    // Test select collapsing
1670    cx.update_editor(|editor, cx| {
1671        editor.move_page_down(&MovePageDown::default(), cx);
1672        editor.move_page_down(&MovePageDown::default(), cx);
1673        editor.move_page_down(&MovePageDown::default(), cx);
1674    });
1675    cx.assert_editor_state(
1676        &r#"
1677        one
1678        two
1679        three
1680        four
1681        five
1682        six
1683        seven
1684        eight
1685        nine
1686        ˇten
1687        ˇ"#
1688        .unindent(),
1689    );
1690}
1691
1692#[gpui::test]
1693async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
1694    init_test(cx, |_| {});
1695    let mut cx = EditorTestContext::new(cx).await;
1696    cx.set_state("one «two threeˇ» four");
1697    cx.update_editor(|editor, cx| {
1698        editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1699        assert_eq!(editor.text(cx), " four");
1700    });
1701}
1702
1703#[gpui::test]
1704fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
1705    init_test(cx, |_| {});
1706
1707    let view = cx.add_window(|cx| {
1708        let buffer = MultiBuffer::build_simple("one two three four", cx);
1709        build_editor(buffer.clone(), cx)
1710    });
1711
1712    _ = view.update(cx, |view, cx| {
1713        view.change_selections(None, cx, |s| {
1714            s.select_display_ranges([
1715                // an empty selection - the preceding word fragment is deleted
1716                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1717                // characters selected - they are deleted
1718                DisplayPoint::new(0, 9)..DisplayPoint::new(0, 12),
1719            ])
1720        });
1721        view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
1722        assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
1723    });
1724
1725    _ = view.update(cx, |view, cx| {
1726        view.change_selections(None, cx, |s| {
1727            s.select_display_ranges([
1728                // an empty selection - the following word fragment is deleted
1729                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1730                // characters selected - they are deleted
1731                DisplayPoint::new(0, 9)..DisplayPoint::new(0, 10),
1732            ])
1733        });
1734        view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
1735        assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
1736    });
1737}
1738
1739#[gpui::test]
1740fn test_newline(cx: &mut TestAppContext) {
1741    init_test(cx, |_| {});
1742
1743    let view = cx.add_window(|cx| {
1744        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
1745        build_editor(buffer.clone(), cx)
1746    });
1747
1748    _ = view.update(cx, |view, cx| {
1749        view.change_selections(None, cx, |s| {
1750            s.select_display_ranges([
1751                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1752                DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
1753                DisplayPoint::new(1, 6)..DisplayPoint::new(1, 6),
1754            ])
1755        });
1756
1757        view.newline(&Newline, cx);
1758        assert_eq!(view.text(cx), "aa\naa\n  \n    bb\n    bb\n");
1759    });
1760}
1761
1762#[gpui::test]
1763fn test_newline_with_old_selections(cx: &mut TestAppContext) {
1764    init_test(cx, |_| {});
1765
1766    let editor = cx.add_window(|cx| {
1767        let buffer = MultiBuffer::build_simple(
1768            "
1769                a
1770                b(
1771                    X
1772                )
1773                c(
1774                    X
1775                )
1776            "
1777            .unindent()
1778            .as_str(),
1779            cx,
1780        );
1781        let mut editor = build_editor(buffer.clone(), cx);
1782        editor.change_selections(None, cx, |s| {
1783            s.select_ranges([
1784                Point::new(2, 4)..Point::new(2, 5),
1785                Point::new(5, 4)..Point::new(5, 5),
1786            ])
1787        });
1788        editor
1789    });
1790
1791    _ = editor.update(cx, |editor, cx| {
1792        // Edit the buffer directly, deleting ranges surrounding the editor's selections
1793        editor.buffer.update(cx, |buffer, cx| {
1794            buffer.edit(
1795                [
1796                    (Point::new(1, 2)..Point::new(3, 0), ""),
1797                    (Point::new(4, 2)..Point::new(6, 0), ""),
1798                ],
1799                None,
1800                cx,
1801            );
1802            assert_eq!(
1803                buffer.read(cx).text(),
1804                "
1805                    a
1806                    b()
1807                    c()
1808                "
1809                .unindent()
1810            );
1811        });
1812        assert_eq!(
1813            editor.selections.ranges(cx),
1814            &[
1815                Point::new(1, 2)..Point::new(1, 2),
1816                Point::new(2, 2)..Point::new(2, 2),
1817            ],
1818        );
1819
1820        editor.newline(&Newline, cx);
1821        assert_eq!(
1822            editor.text(cx),
1823            "
1824                a
1825                b(
1826                )
1827                c(
1828                )
1829            "
1830            .unindent()
1831        );
1832
1833        // The selections are moved after the inserted newlines
1834        assert_eq!(
1835            editor.selections.ranges(cx),
1836            &[
1837                Point::new(2, 0)..Point::new(2, 0),
1838                Point::new(4, 0)..Point::new(4, 0),
1839            ],
1840        );
1841    });
1842}
1843
1844#[gpui::test]
1845async fn test_newline_above(cx: &mut gpui::TestAppContext) {
1846    init_test(cx, |settings| {
1847        settings.defaults.tab_size = NonZeroU32::new(4)
1848    });
1849
1850    let language = Arc::new(
1851        Language::new(
1852            LanguageConfig::default(),
1853            Some(tree_sitter_rust::language()),
1854        )
1855        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1856        .unwrap(),
1857    );
1858
1859    let mut cx = EditorTestContext::new(cx).await;
1860    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1861    cx.set_state(indoc! {"
1862        const a: ˇA = (
18631864                «const_functionˇ»(ˇ),
1865                so«mˇ»et«hˇ»ing_ˇelse,ˇ
18661867        ˇ);ˇ
1868    "});
1869
1870    cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
1871    cx.assert_editor_state(indoc! {"
1872        ˇ
1873        const a: A = (
1874            ˇ
1875            (
1876                ˇ
1877                ˇ
1878                const_function(),
1879                ˇ
1880                ˇ
1881                ˇ
1882                ˇ
1883                something_else,
1884                ˇ
1885            )
1886            ˇ
1887            ˇ
1888        );
1889    "});
1890}
1891
1892#[gpui::test]
1893async fn test_newline_below(cx: &mut gpui::TestAppContext) {
1894    init_test(cx, |settings| {
1895        settings.defaults.tab_size = NonZeroU32::new(4)
1896    });
1897
1898    let language = Arc::new(
1899        Language::new(
1900            LanguageConfig::default(),
1901            Some(tree_sitter_rust::language()),
1902        )
1903        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1904        .unwrap(),
1905    );
1906
1907    let mut cx = EditorTestContext::new(cx).await;
1908    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1909    cx.set_state(indoc! {"
1910        const a: ˇA = (
19111912                «const_functionˇ»(ˇ),
1913                so«mˇ»et«hˇ»ing_ˇelse,ˇ
19141915        ˇ);ˇ
1916    "});
1917
1918    cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
1919    cx.assert_editor_state(indoc! {"
1920        const a: A = (
1921            ˇ
1922            (
1923                ˇ
1924                const_function(),
1925                ˇ
1926                ˇ
1927                something_else,
1928                ˇ
1929                ˇ
1930                ˇ
1931                ˇ
1932            )
1933            ˇ
1934        );
1935        ˇ
1936        ˇ
1937    "});
1938}
1939
1940#[gpui::test]
1941async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
1942    init_test(cx, |settings| {
1943        settings.defaults.tab_size = NonZeroU32::new(4)
1944    });
1945
1946    let language = Arc::new(Language::new(
1947        LanguageConfig {
1948            line_comments: vec!["//".into()],
1949            ..LanguageConfig::default()
1950        },
1951        None,
1952    ));
1953    {
1954        let mut cx = EditorTestContext::new(cx).await;
1955        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1956        cx.set_state(indoc! {"
1957        // Fooˇ
1958    "});
1959
1960        cx.update_editor(|e, cx| e.newline(&Newline, cx));
1961        cx.assert_editor_state(indoc! {"
1962        // Foo
1963        //ˇ
1964    "});
1965        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
1966        cx.set_state(indoc! {"
1967        ˇ// Foo
1968    "});
1969        cx.update_editor(|e, cx| e.newline(&Newline, cx));
1970        cx.assert_editor_state(indoc! {"
1971
1972        ˇ// Foo
1973    "});
1974    }
1975    // Ensure that comment continuations can be disabled.
1976    update_test_language_settings(cx, |settings| {
1977        settings.defaults.extend_comment_on_newline = Some(false);
1978    });
1979    let mut cx = EditorTestContext::new(cx).await;
1980    cx.set_state(indoc! {"
1981        // Fooˇ
1982    "});
1983    cx.update_editor(|e, cx| e.newline(&Newline, cx));
1984    cx.assert_editor_state(indoc! {"
1985        // Foo
1986        ˇ
1987    "});
1988}
1989
1990#[gpui::test]
1991fn test_insert_with_old_selections(cx: &mut TestAppContext) {
1992    init_test(cx, |_| {});
1993
1994    let editor = cx.add_window(|cx| {
1995        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
1996        let mut editor = build_editor(buffer.clone(), cx);
1997        editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
1998        editor
1999    });
2000
2001    _ = editor.update(cx, |editor, cx| {
2002        // Edit the buffer directly, deleting ranges surrounding the editor's selections
2003        editor.buffer.update(cx, |buffer, cx| {
2004            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2005            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2006        });
2007        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2008
2009        editor.insert("Z", cx);
2010        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2011
2012        // The selections are moved after the inserted characters
2013        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2014    });
2015}
2016
2017#[gpui::test]
2018async fn test_tab(cx: &mut gpui::TestAppContext) {
2019    init_test(cx, |settings| {
2020        settings.defaults.tab_size = NonZeroU32::new(3)
2021    });
2022
2023    let mut cx = EditorTestContext::new(cx).await;
2024    cx.set_state(indoc! {"
2025        ˇabˇc
2026        ˇ🏀ˇ🏀ˇefg
20272028    "});
2029    cx.update_editor(|e, cx| e.tab(&Tab, cx));
2030    cx.assert_editor_state(indoc! {"
2031           ˇab ˇc
2032           ˇ🏀  ˇ🏀  ˇefg
2033        d  ˇ
2034    "});
2035
2036    cx.set_state(indoc! {"
2037        a
2038        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2039    "});
2040    cx.update_editor(|e, cx| e.tab(&Tab, cx));
2041    cx.assert_editor_state(indoc! {"
2042        a
2043           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2044    "});
2045}
2046
2047#[gpui::test]
2048async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2049    init_test(cx, |_| {});
2050
2051    let mut cx = EditorTestContext::new(cx).await;
2052    let language = Arc::new(
2053        Language::new(
2054            LanguageConfig::default(),
2055            Some(tree_sitter_rust::language()),
2056        )
2057        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2058        .unwrap(),
2059    );
2060    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2061
2062    // cursors that are already at the suggested indent level insert
2063    // a soft tab. cursors that are to the left of the suggested indent
2064    // auto-indent their line.
2065    cx.set_state(indoc! {"
2066        ˇ
2067        const a: B = (
2068            c(
2069                d(
2070        ˇ
2071                )
2072        ˇ
2073        ˇ    )
2074        );
2075    "});
2076    cx.update_editor(|e, cx| e.tab(&Tab, cx));
2077    cx.assert_editor_state(indoc! {"
2078            ˇ
2079        const a: B = (
2080            c(
2081                d(
2082                    ˇ
2083                )
2084                ˇ
2085            ˇ)
2086        );
2087    "});
2088
2089    // handle auto-indent when there are multiple cursors on the same line
2090    cx.set_state(indoc! {"
2091        const a: B = (
2092            c(
2093        ˇ    ˇ
2094        ˇ    )
2095        );
2096    "});
2097    cx.update_editor(|e, cx| e.tab(&Tab, cx));
2098    cx.assert_editor_state(indoc! {"
2099        const a: B = (
2100            c(
2101                ˇ
2102            ˇ)
2103        );
2104    "});
2105}
2106
2107#[gpui::test]
2108async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2109    init_test(cx, |settings| {
2110        settings.defaults.tab_size = NonZeroU32::new(4)
2111    });
2112
2113    let language = Arc::new(
2114        Language::new(
2115            LanguageConfig::default(),
2116            Some(tree_sitter_rust::language()),
2117        )
2118        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2119        .unwrap(),
2120    );
2121
2122    let mut cx = EditorTestContext::new(cx).await;
2123    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2124    cx.set_state(indoc! {"
2125        fn a() {
2126            if b {
2127        \t ˇc
2128            }
2129        }
2130    "});
2131
2132    cx.update_editor(|e, cx| e.tab(&Tab, cx));
2133    cx.assert_editor_state(indoc! {"
2134        fn a() {
2135            if b {
2136                ˇc
2137            }
2138        }
2139    "});
2140}
2141
2142#[gpui::test]
2143async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2144    init_test(cx, |settings| {
2145        settings.defaults.tab_size = NonZeroU32::new(4);
2146    });
2147
2148    let mut cx = EditorTestContext::new(cx).await;
2149
2150    cx.set_state(indoc! {"
2151          «oneˇ» «twoˇ»
2152        three
2153         four
2154    "});
2155    cx.update_editor(|e, cx| e.tab(&Tab, cx));
2156    cx.assert_editor_state(indoc! {"
2157            «oneˇ» «twoˇ»
2158        three
2159         four
2160    "});
2161
2162    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2163    cx.assert_editor_state(indoc! {"
2164        «oneˇ» «twoˇ»
2165        three
2166         four
2167    "});
2168
2169    // select across line ending
2170    cx.set_state(indoc! {"
2171        one two
2172        t«hree
2173        ˇ» four
2174    "});
2175    cx.update_editor(|e, cx| e.tab(&Tab, cx));
2176    cx.assert_editor_state(indoc! {"
2177        one two
2178            t«hree
2179        ˇ» four
2180    "});
2181
2182    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2183    cx.assert_editor_state(indoc! {"
2184        one two
2185        t«hree
2186        ˇ» four
2187    "});
2188
2189    // Ensure that indenting/outdenting works when the cursor is at column 0.
2190    cx.set_state(indoc! {"
2191        one two
2192        ˇthree
2193            four
2194    "});
2195    cx.update_editor(|e, cx| e.tab(&Tab, cx));
2196    cx.assert_editor_state(indoc! {"
2197        one two
2198            ˇthree
2199            four
2200    "});
2201
2202    cx.set_state(indoc! {"
2203        one two
2204        ˇ    three
2205            four
2206    "});
2207    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2208    cx.assert_editor_state(indoc! {"
2209        one two
2210        ˇthree
2211            four
2212    "});
2213}
2214
2215#[gpui::test]
2216async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2217    init_test(cx, |settings| {
2218        settings.defaults.hard_tabs = Some(true);
2219    });
2220
2221    let mut cx = EditorTestContext::new(cx).await;
2222
2223    // select two ranges on one line
2224    cx.set_state(indoc! {"
2225        «oneˇ» «twoˇ»
2226        three
2227        four
2228    "});
2229    cx.update_editor(|e, cx| e.tab(&Tab, cx));
2230    cx.assert_editor_state(indoc! {"
2231        \t«oneˇ» «twoˇ»
2232        three
2233        four
2234    "});
2235    cx.update_editor(|e, cx| e.tab(&Tab, cx));
2236    cx.assert_editor_state(indoc! {"
2237        \t\t«oneˇ» «twoˇ»
2238        three
2239        four
2240    "});
2241    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2242    cx.assert_editor_state(indoc! {"
2243        \t«oneˇ» «twoˇ»
2244        three
2245        four
2246    "});
2247    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2248    cx.assert_editor_state(indoc! {"
2249        «oneˇ» «twoˇ»
2250        three
2251        four
2252    "});
2253
2254    // select across a line ending
2255    cx.set_state(indoc! {"
2256        one two
2257        t«hree
2258        ˇ»four
2259    "});
2260    cx.update_editor(|e, cx| e.tab(&Tab, cx));
2261    cx.assert_editor_state(indoc! {"
2262        one two
2263        \tt«hree
2264        ˇ»four
2265    "});
2266    cx.update_editor(|e, cx| e.tab(&Tab, cx));
2267    cx.assert_editor_state(indoc! {"
2268        one two
2269        \t\tt«hree
2270        ˇ»four
2271    "});
2272    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2273    cx.assert_editor_state(indoc! {"
2274        one two
2275        \tt«hree
2276        ˇ»four
2277    "});
2278    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2279    cx.assert_editor_state(indoc! {"
2280        one two
2281        t«hree
2282        ˇ»four
2283    "});
2284
2285    // Ensure that indenting/outdenting works when the cursor is at column 0.
2286    cx.set_state(indoc! {"
2287        one two
2288        ˇthree
2289        four
2290    "});
2291    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2292    cx.assert_editor_state(indoc! {"
2293        one two
2294        ˇthree
2295        four
2296    "});
2297    cx.update_editor(|e, cx| e.tab(&Tab, cx));
2298    cx.assert_editor_state(indoc! {"
2299        one two
2300        \tˇthree
2301        four
2302    "});
2303    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2304    cx.assert_editor_state(indoc! {"
2305        one two
2306        ˇthree
2307        four
2308    "});
2309}
2310
2311#[gpui::test]
2312fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2313    init_test(cx, |settings| {
2314        settings.languages.extend([
2315            (
2316                "TOML".into(),
2317                LanguageSettingsContent {
2318                    tab_size: NonZeroU32::new(2),
2319                    ..Default::default()
2320                },
2321            ),
2322            (
2323                "Rust".into(),
2324                LanguageSettingsContent {
2325                    tab_size: NonZeroU32::new(4),
2326                    ..Default::default()
2327                },
2328            ),
2329        ]);
2330    });
2331
2332    let toml_language = Arc::new(Language::new(
2333        LanguageConfig {
2334            name: "TOML".into(),
2335            ..Default::default()
2336        },
2337        None,
2338    ));
2339    let rust_language = Arc::new(Language::new(
2340        LanguageConfig {
2341            name: "Rust".into(),
2342            ..Default::default()
2343        },
2344        None,
2345    ));
2346
2347    let toml_buffer = cx.new_model(|cx| {
2348        Buffer::new(
2349            0,
2350            BufferId::new(cx.entity_id().as_u64()).unwrap(),
2351            "a = 1\nb = 2\n",
2352        )
2353        .with_language(toml_language, cx)
2354    });
2355    let rust_buffer = cx.new_model(|cx| {
2356        Buffer::new(
2357            0,
2358            BufferId::new(cx.entity_id().as_u64()).unwrap(),
2359            "const c: usize = 3;\n",
2360        )
2361        .with_language(rust_language, cx)
2362    });
2363    let multibuffer = cx.new_model(|cx| {
2364        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
2365        multibuffer.push_excerpts(
2366            toml_buffer.clone(),
2367            [ExcerptRange {
2368                context: Point::new(0, 0)..Point::new(2, 0),
2369                primary: None,
2370            }],
2371            cx,
2372        );
2373        multibuffer.push_excerpts(
2374            rust_buffer.clone(),
2375            [ExcerptRange {
2376                context: Point::new(0, 0)..Point::new(1, 0),
2377                primary: None,
2378            }],
2379            cx,
2380        );
2381        multibuffer
2382    });
2383
2384    cx.add_window(|cx| {
2385        let mut editor = build_editor(multibuffer, cx);
2386
2387        assert_eq!(
2388            editor.text(cx),
2389            indoc! {"
2390                a = 1
2391                b = 2
2392
2393                const c: usize = 3;
2394            "}
2395        );
2396
2397        select_ranges(
2398            &mut editor,
2399            indoc! {"
2400                «aˇ» = 1
2401                b = 2
2402
2403                «const c:ˇ» usize = 3;
2404            "},
2405            cx,
2406        );
2407
2408        editor.tab(&Tab, cx);
2409        assert_text_with_selections(
2410            &mut editor,
2411            indoc! {"
2412                  «aˇ» = 1
2413                b = 2
2414
2415                    «const c:ˇ» usize = 3;
2416            "},
2417            cx,
2418        );
2419        editor.tab_prev(&TabPrev, cx);
2420        assert_text_with_selections(
2421            &mut editor,
2422            indoc! {"
2423                «aˇ» = 1
2424                b = 2
2425
2426                «const c:ˇ» usize = 3;
2427            "},
2428            cx,
2429        );
2430
2431        editor
2432    });
2433}
2434
2435#[gpui::test]
2436async fn test_backspace(cx: &mut gpui::TestAppContext) {
2437    init_test(cx, |_| {});
2438
2439    let mut cx = EditorTestContext::new(cx).await;
2440
2441    // Basic backspace
2442    cx.set_state(indoc! {"
2443        onˇe two three
2444        fou«rˇ» five six
2445        seven «ˇeight nine
2446        »ten
2447    "});
2448    cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2449    cx.assert_editor_state(indoc! {"
2450        oˇe two three
2451        fouˇ five six
2452        seven ˇten
2453    "});
2454
2455    // Test backspace inside and around indents
2456    cx.set_state(indoc! {"
2457        zero
2458            ˇone
2459                ˇtwo
2460            ˇ ˇ ˇ  three
2461        ˇ  ˇ  four
2462    "});
2463    cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2464    cx.assert_editor_state(indoc! {"
2465        zero
2466        ˇone
2467            ˇtwo
2468        ˇ  threeˇ  four
2469    "});
2470
2471    // Test backspace with line_mode set to true
2472    cx.update_editor(|e, _| e.selections.line_mode = true);
2473    cx.set_state(indoc! {"
2474        The ˇquick ˇbrown
2475        fox jumps over
2476        the lazy dog
2477        ˇThe qu«ick bˇ»rown"});
2478    cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2479    cx.assert_editor_state(indoc! {"
2480        ˇfox jumps over
2481        the lazy dogˇ"});
2482}
2483
2484#[gpui::test]
2485async fn test_delete(cx: &mut gpui::TestAppContext) {
2486    init_test(cx, |_| {});
2487
2488    let mut cx = EditorTestContext::new(cx).await;
2489    cx.set_state(indoc! {"
2490        onˇe two three
2491        fou«rˇ» five six
2492        seven «ˇeight nine
2493        »ten
2494    "});
2495    cx.update_editor(|e, cx| e.delete(&Delete, cx));
2496    cx.assert_editor_state(indoc! {"
2497        onˇ two three
2498        fouˇ five six
2499        seven ˇten
2500    "});
2501
2502    // Test backspace with line_mode set to true
2503    cx.update_editor(|e, _| e.selections.line_mode = true);
2504    cx.set_state(indoc! {"
2505        The ˇquick ˇbrown
2506        fox «ˇjum»ps over
2507        the lazy dog
2508        ˇThe qu«ick bˇ»rown"});
2509    cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2510    cx.assert_editor_state("ˇthe lazy dogˇ");
2511}
2512
2513#[gpui::test]
2514fn test_delete_line(cx: &mut TestAppContext) {
2515    init_test(cx, |_| {});
2516
2517    let view = cx.add_window(|cx| {
2518        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2519        build_editor(buffer, cx)
2520    });
2521    _ = view.update(cx, |view, cx| {
2522        view.change_selections(None, cx, |s| {
2523            s.select_display_ranges([
2524                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2525                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2526                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2527            ])
2528        });
2529        view.delete_line(&DeleteLine, cx);
2530        assert_eq!(view.display_text(cx), "ghi");
2531        assert_eq!(
2532            view.selections.display_ranges(cx),
2533            vec![
2534                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
2535                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)
2536            ]
2537        );
2538    });
2539
2540    let view = cx.add_window(|cx| {
2541        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2542        build_editor(buffer, cx)
2543    });
2544    _ = view.update(cx, |view, cx| {
2545        view.change_selections(None, cx, |s| {
2546            s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
2547        });
2548        view.delete_line(&DeleteLine, cx);
2549        assert_eq!(view.display_text(cx), "ghi\n");
2550        assert_eq!(
2551            view.selections.display_ranges(cx),
2552            vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]
2553        );
2554    });
2555}
2556
2557#[gpui::test]
2558fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
2559    init_test(cx, |_| {});
2560
2561    cx.add_window(|cx| {
2562        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
2563        let mut editor = build_editor(buffer.clone(), cx);
2564        let buffer = buffer.read(cx).as_singleton().unwrap();
2565
2566        assert_eq!(
2567            editor.selections.ranges::<Point>(cx),
2568            &[Point::new(0, 0)..Point::new(0, 0)]
2569        );
2570
2571        // When on single line, replace newline at end by space
2572        editor.join_lines(&JoinLines, cx);
2573        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2574        assert_eq!(
2575            editor.selections.ranges::<Point>(cx),
2576            &[Point::new(0, 3)..Point::new(0, 3)]
2577        );
2578
2579        // When multiple lines are selected, remove newlines that are spanned by the selection
2580        editor.change_selections(None, cx, |s| {
2581            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
2582        });
2583        editor.join_lines(&JoinLines, cx);
2584        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
2585        assert_eq!(
2586            editor.selections.ranges::<Point>(cx),
2587            &[Point::new(0, 11)..Point::new(0, 11)]
2588        );
2589
2590        // Undo should be transactional
2591        editor.undo(&Undo, cx);
2592        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2593        assert_eq!(
2594            editor.selections.ranges::<Point>(cx),
2595            &[Point::new(0, 5)..Point::new(2, 2)]
2596        );
2597
2598        // When joining an empty line don't insert a space
2599        editor.change_selections(None, cx, |s| {
2600            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
2601        });
2602        editor.join_lines(&JoinLines, cx);
2603        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
2604        assert_eq!(
2605            editor.selections.ranges::<Point>(cx),
2606            [Point::new(2, 3)..Point::new(2, 3)]
2607        );
2608
2609        // We can remove trailing newlines
2610        editor.join_lines(&JoinLines, cx);
2611        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2612        assert_eq!(
2613            editor.selections.ranges::<Point>(cx),
2614            [Point::new(2, 3)..Point::new(2, 3)]
2615        );
2616
2617        // We don't blow up on the last line
2618        editor.join_lines(&JoinLines, cx);
2619        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2620        assert_eq!(
2621            editor.selections.ranges::<Point>(cx),
2622            [Point::new(2, 3)..Point::new(2, 3)]
2623        );
2624
2625        // reset to test indentation
2626        editor.buffer.update(cx, |buffer, cx| {
2627            buffer.edit(
2628                [
2629                    (Point::new(1, 0)..Point::new(1, 2), "  "),
2630                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
2631                ],
2632                None,
2633                cx,
2634            )
2635        });
2636
2637        // We remove any leading spaces
2638        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
2639        editor.change_selections(None, cx, |s| {
2640            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
2641        });
2642        editor.join_lines(&JoinLines, cx);
2643        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
2644
2645        // We don't insert a space for a line containing only spaces
2646        editor.join_lines(&JoinLines, cx);
2647        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
2648
2649        // We ignore any leading tabs
2650        editor.join_lines(&JoinLines, cx);
2651        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
2652
2653        editor
2654    });
2655}
2656
2657#[gpui::test]
2658fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
2659    init_test(cx, |_| {});
2660
2661    cx.add_window(|cx| {
2662        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
2663        let mut editor = build_editor(buffer.clone(), cx);
2664        let buffer = buffer.read(cx).as_singleton().unwrap();
2665
2666        editor.change_selections(None, cx, |s| {
2667            s.select_ranges([
2668                Point::new(0, 2)..Point::new(1, 1),
2669                Point::new(1, 2)..Point::new(1, 2),
2670                Point::new(3, 1)..Point::new(3, 2),
2671            ])
2672        });
2673
2674        editor.join_lines(&JoinLines, cx);
2675        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
2676
2677        assert_eq!(
2678            editor.selections.ranges::<Point>(cx),
2679            [
2680                Point::new(0, 7)..Point::new(0, 7),
2681                Point::new(1, 3)..Point::new(1, 3)
2682            ]
2683        );
2684        editor
2685    });
2686}
2687
2688#[gpui::test]
2689async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
2690    init_test(cx, |_| {});
2691
2692    let mut cx = EditorTestContext::new(cx).await;
2693
2694    // Test sort_lines_case_insensitive()
2695    cx.set_state(indoc! {"
2696        «z
2697        y
2698        x
2699        Z
2700        Y
2701        Xˇ»
2702    "});
2703    cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
2704    cx.assert_editor_state(indoc! {"
2705        «x
2706        X
2707        y
2708        Y
2709        z
2710        Zˇ»
2711    "});
2712
2713    // Test reverse_lines()
2714    cx.set_state(indoc! {"
2715        «5
2716        4
2717        3
2718        2
2719        1ˇ»
2720    "});
2721    cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
2722    cx.assert_editor_state(indoc! {"
2723        «1
2724        2
2725        3
2726        4
2727        5ˇ»
2728    "});
2729
2730    // Skip testing shuffle_line()
2731
2732    // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
2733    // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
2734
2735    // Don't manipulate when cursor is on single line, but expand the selection
2736    cx.set_state(indoc! {"
2737        ddˇdd
2738        ccc
2739        bb
2740        a
2741    "});
2742    cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2743    cx.assert_editor_state(indoc! {"
2744        «ddddˇ»
2745        ccc
2746        bb
2747        a
2748    "});
2749
2750    // Basic manipulate case
2751    // Start selection moves to column 0
2752    // End of selection shrinks to fit shorter line
2753    cx.set_state(indoc! {"
2754        dd«d
2755        ccc
2756        bb
2757        aaaaaˇ»
2758    "});
2759    cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2760    cx.assert_editor_state(indoc! {"
2761        «aaaaa
2762        bb
2763        ccc
2764        dddˇ»
2765    "});
2766
2767    // Manipulate case with newlines
2768    cx.set_state(indoc! {"
2769        dd«d
2770        ccc
2771
2772        bb
2773        aaaaa
2774
2775        ˇ»
2776    "});
2777    cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2778    cx.assert_editor_state(indoc! {"
2779        «
2780
2781        aaaaa
2782        bb
2783        ccc
2784        dddˇ»
2785
2786    "});
2787
2788    // Adding new line
2789    cx.set_state(indoc! {"
2790        aa«a
2791        bbˇ»b
2792    "});
2793    cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
2794    cx.assert_editor_state(indoc! {"
2795        «aaa
2796        bbb
2797        added_lineˇ»
2798    "});
2799
2800    // Removing line
2801    cx.set_state(indoc! {"
2802        aa«a
2803        bbbˇ»
2804    "});
2805    cx.update_editor(|e, cx| {
2806        e.manipulate_lines(cx, |lines| {
2807            lines.pop();
2808        })
2809    });
2810    cx.assert_editor_state(indoc! {"
2811        «aaaˇ»
2812    "});
2813
2814    // Removing all lines
2815    cx.set_state(indoc! {"
2816        aa«a
2817        bbbˇ»
2818    "});
2819    cx.update_editor(|e, cx| {
2820        e.manipulate_lines(cx, |lines| {
2821            lines.drain(..);
2822        })
2823    });
2824    cx.assert_editor_state(indoc! {"
2825        ˇ
2826    "});
2827}
2828
2829#[gpui::test]
2830async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
2831    init_test(cx, |_| {});
2832
2833    let mut cx = EditorTestContext::new(cx).await;
2834
2835    // Consider continuous selection as single selection
2836    cx.set_state(indoc! {"
2837        Aaa«aa
2838        cˇ»c«c
2839        bb
2840        aaaˇ»aa
2841    "});
2842    cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
2843    cx.assert_editor_state(indoc! {"
2844        «Aaaaa
2845        ccc
2846        bb
2847        aaaaaˇ»
2848    "});
2849
2850    cx.set_state(indoc! {"
2851        Aaa«aa
2852        cˇ»c«c
2853        bb
2854        aaaˇ»aa
2855    "});
2856    cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
2857    cx.assert_editor_state(indoc! {"
2858        «Aaaaa
2859        ccc
2860        bbˇ»
2861    "});
2862
2863    // Consider non continuous selection as distinct dedup operations
2864    cx.set_state(indoc! {"
2865        «aaaaa
2866        bb
2867        aaaaa
2868        aaaaaˇ»
2869
2870        aaa«aaˇ»
2871    "});
2872    cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
2873    cx.assert_editor_state(indoc! {"
2874        «aaaaa
2875        bbˇ»
2876
2877        «aaaaaˇ»
2878    "});
2879}
2880
2881#[gpui::test]
2882async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
2883    init_test(cx, |_| {});
2884
2885    let mut cx = EditorTestContext::new(cx).await;
2886
2887    cx.set_state(indoc! {"
2888        «Aaa
2889        aAa
2890        Aaaˇ»
2891    "});
2892    cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
2893    cx.assert_editor_state(indoc! {"
2894        «Aaa
2895        aAaˇ»
2896    "});
2897
2898    cx.set_state(indoc! {"
2899        «Aaa
2900        aAa
2901        aaAˇ»
2902    "});
2903    cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
2904    cx.assert_editor_state(indoc! {"
2905        «Aaaˇ»
2906    "});
2907}
2908
2909#[gpui::test]
2910async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
2911    init_test(cx, |_| {});
2912
2913    let mut cx = EditorTestContext::new(cx).await;
2914
2915    // Manipulate with multiple selections on a single line
2916    cx.set_state(indoc! {"
2917        dd«dd
2918        cˇ»c«c
2919        bb
2920        aaaˇ»aa
2921    "});
2922    cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2923    cx.assert_editor_state(indoc! {"
2924        «aaaaa
2925        bb
2926        ccc
2927        ddddˇ»
2928    "});
2929
2930    // Manipulate with multiple disjoin selections
2931    cx.set_state(indoc! {"
29322933        4
2934        3
2935        2
2936        1ˇ»
2937
2938        dd«dd
2939        ccc
2940        bb
2941        aaaˇ»aa
2942    "});
2943    cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2944    cx.assert_editor_state(indoc! {"
2945        «1
2946        2
2947        3
2948        4
2949        5ˇ»
2950
2951        «aaaaa
2952        bb
2953        ccc
2954        ddddˇ»
2955    "});
2956
2957    // Adding lines on each selection
2958    cx.set_state(indoc! {"
29592960        1ˇ»
2961
2962        bb«bb
2963        aaaˇ»aa
2964    "});
2965    cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
2966    cx.assert_editor_state(indoc! {"
2967        «2
2968        1
2969        added lineˇ»
2970
2971        «bbbb
2972        aaaaa
2973        added lineˇ»
2974    "});
2975
2976    // Removing lines on each selection
2977    cx.set_state(indoc! {"
29782979        1ˇ»
2980
2981        bb«bb
2982        aaaˇ»aa
2983    "});
2984    cx.update_editor(|e, cx| {
2985        e.manipulate_lines(cx, |lines| {
2986            lines.pop();
2987        })
2988    });
2989    cx.assert_editor_state(indoc! {"
2990        «2ˇ»
2991
2992        «bbbbˇ»
2993    "});
2994}
2995
2996#[gpui::test]
2997async fn test_manipulate_text(cx: &mut TestAppContext) {
2998    init_test(cx, |_| {});
2999
3000    let mut cx = EditorTestContext::new(cx).await;
3001
3002    // Test convert_to_upper_case()
3003    cx.set_state(indoc! {"
3004        «hello worldˇ»
3005    "});
3006    cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3007    cx.assert_editor_state(indoc! {"
3008        «HELLO WORLDˇ»
3009    "});
3010
3011    // Test convert_to_lower_case()
3012    cx.set_state(indoc! {"
3013        «HELLO WORLDˇ»
3014    "});
3015    cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3016    cx.assert_editor_state(indoc! {"
3017        «hello worldˇ»
3018    "});
3019
3020    // Test multiple line, single selection case
3021    // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3022    cx.set_state(indoc! {"
3023        «The quick brown
3024        fox jumps over
3025        the lazy dogˇ»
3026    "});
3027    cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3028    cx.assert_editor_state(indoc! {"
3029        «The Quick Brown
3030        Fox Jumps Over
3031        The Lazy Dogˇ»
3032    "});
3033
3034    // Test multiple line, single selection case
3035    // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3036    cx.set_state(indoc! {"
3037        «The quick brown
3038        fox jumps over
3039        the lazy dogˇ»
3040    "});
3041    cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3042    cx.assert_editor_state(indoc! {"
3043        «TheQuickBrown
3044        FoxJumpsOver
3045        TheLazyDogˇ»
3046    "});
3047
3048    // From here on out, test more complex cases of manipulate_text()
3049
3050    // Test no selection case - should affect words cursors are in
3051    // Cursor at beginning, middle, and end of word
3052    cx.set_state(indoc! {"
3053        ˇhello big beauˇtiful worldˇ
3054    "});
3055    cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3056    cx.assert_editor_state(indoc! {"
3057        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3058    "});
3059
3060    // Test multiple selections on a single line and across multiple lines
3061    cx.set_state(indoc! {"
3062        «Theˇ» quick «brown
3063        foxˇ» jumps «overˇ»
3064        the «lazyˇ» dog
3065    "});
3066    cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3067    cx.assert_editor_state(indoc! {"
3068        «THEˇ» quick «BROWN
3069        FOXˇ» jumps «OVERˇ»
3070        the «LAZYˇ» dog
3071    "});
3072
3073    // Test case where text length grows
3074    cx.set_state(indoc! {"
3075        «tschüߡ»
3076    "});
3077    cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3078    cx.assert_editor_state(indoc! {"
3079        «TSCHÜSSˇ»
3080    "});
3081
3082    // Test to make sure we don't crash when text shrinks
3083    cx.set_state(indoc! {"
3084        aaa_bbbˇ
3085    "});
3086    cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3087    cx.assert_editor_state(indoc! {"
3088        «aaaBbbˇ»
3089    "});
3090
3091    // Test to make sure we all aware of the fact that each word can grow and shrink
3092    // Final selections should be aware of this fact
3093    cx.set_state(indoc! {"
3094        aaa_bˇbb bbˇb_ccc ˇccc_ddd
3095    "});
3096    cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3097    cx.assert_editor_state(indoc! {"
3098        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3099    "});
3100}
3101
3102#[gpui::test]
3103fn test_duplicate_line(cx: &mut TestAppContext) {
3104    init_test(cx, |_| {});
3105
3106    let view = cx.add_window(|cx| {
3107        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3108        build_editor(buffer, cx)
3109    });
3110    _ = view.update(cx, |view, cx| {
3111        view.change_selections(None, cx, |s| {
3112            s.select_display_ranges([
3113                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3114                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3115                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
3116                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
3117            ])
3118        });
3119        view.duplicate_line(&DuplicateLine::default(), cx);
3120        assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3121        assert_eq!(
3122            view.selections.display_ranges(cx),
3123            vec![
3124                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
3125                DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
3126                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
3127                DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
3128            ]
3129        );
3130    });
3131
3132    let view = cx.add_window(|cx| {
3133        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3134        build_editor(buffer, cx)
3135    });
3136    _ = view.update(cx, |view, cx| {
3137        view.change_selections(None, cx, |s| {
3138            s.select_display_ranges([
3139                DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
3140                DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
3141            ])
3142        });
3143        view.duplicate_line(&DuplicateLine::default(), cx);
3144        assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3145        assert_eq!(
3146            view.selections.display_ranges(cx),
3147            vec![
3148                DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1),
3149                DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
3150            ]
3151        );
3152    });
3153
3154    // With `move_upwards` the selections stay in place, except for
3155    // the lines inserted above them
3156    let view = cx.add_window(|cx| {
3157        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3158        build_editor(buffer, cx)
3159    });
3160    _ = view.update(cx, |view, cx| {
3161        view.change_selections(None, cx, |s| {
3162            s.select_display_ranges([
3163                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3164                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3165                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
3166                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
3167            ])
3168        });
3169        view.duplicate_line(&DuplicateLine { move_upwards: true }, cx);
3170        assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3171        assert_eq!(
3172            view.selections.display_ranges(cx),
3173            vec![
3174                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3175                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3176                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
3177                DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
3178            ]
3179        );
3180    });
3181
3182    let view = cx.add_window(|cx| {
3183        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3184        build_editor(buffer, cx)
3185    });
3186    _ = view.update(cx, |view, cx| {
3187        view.change_selections(None, cx, |s| {
3188            s.select_display_ranges([
3189                DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
3190                DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
3191            ])
3192        });
3193        view.duplicate_line(&DuplicateLine { move_upwards: true }, cx);
3194        assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3195        assert_eq!(
3196            view.selections.display_ranges(cx),
3197            vec![
3198                DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
3199                DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
3200            ]
3201        );
3202    });
3203}
3204
3205#[gpui::test]
3206fn test_move_line_up_down(cx: &mut TestAppContext) {
3207    init_test(cx, |_| {});
3208
3209    let view = cx.add_window(|cx| {
3210        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3211        build_editor(buffer, cx)
3212    });
3213    _ = view.update(cx, |view, cx| {
3214        view.fold_ranges(
3215            vec![
3216                Point::new(0, 2)..Point::new(1, 2),
3217                Point::new(2, 3)..Point::new(4, 1),
3218                Point::new(7, 0)..Point::new(8, 4),
3219            ],
3220            true,
3221            cx,
3222        );
3223        view.change_selections(None, cx, |s| {
3224            s.select_display_ranges([
3225                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
3226                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
3227                DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
3228                DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2),
3229            ])
3230        });
3231        assert_eq!(
3232            view.display_text(cx),
3233            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3234        );
3235
3236        view.move_line_up(&MoveLineUp, cx);
3237        assert_eq!(
3238            view.display_text(cx),
3239            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3240        );
3241        assert_eq!(
3242            view.selections.display_ranges(cx),
3243            vec![
3244                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
3245                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
3246                DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
3247                DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
3248            ]
3249        );
3250    });
3251
3252    _ = view.update(cx, |view, cx| {
3253        view.move_line_down(&MoveLineDown, cx);
3254        assert_eq!(
3255            view.display_text(cx),
3256            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3257        );
3258        assert_eq!(
3259            view.selections.display_ranges(cx),
3260            vec![
3261                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
3262                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
3263                DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
3264                DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
3265            ]
3266        );
3267    });
3268
3269    _ = view.update(cx, |view, cx| {
3270        view.move_line_down(&MoveLineDown, cx);
3271        assert_eq!(
3272            view.display_text(cx),
3273            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3274        );
3275        assert_eq!(
3276            view.selections.display_ranges(cx),
3277            vec![
3278                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
3279                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
3280                DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
3281                DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
3282            ]
3283        );
3284    });
3285
3286    _ = view.update(cx, |view, cx| {
3287        view.move_line_up(&MoveLineUp, cx);
3288        assert_eq!(
3289            view.display_text(cx),
3290            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3291        );
3292        assert_eq!(
3293            view.selections.display_ranges(cx),
3294            vec![
3295                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
3296                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
3297                DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
3298                DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
3299            ]
3300        );
3301    });
3302}
3303
3304#[gpui::test]
3305fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3306    init_test(cx, |_| {});
3307
3308    let editor = cx.add_window(|cx| {
3309        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3310        build_editor(buffer, cx)
3311    });
3312    _ = editor.update(cx, |editor, cx| {
3313        let snapshot = editor.buffer.read(cx).snapshot(cx);
3314        editor.insert_blocks(
3315            [BlockProperties {
3316                style: BlockStyle::Fixed,
3317                position: snapshot.anchor_after(Point::new(2, 0)),
3318                disposition: BlockDisposition::Below,
3319                height: 1,
3320                render: Arc::new(|_| div().into_any()),
3321            }],
3322            Some(Autoscroll::fit()),
3323            cx,
3324        );
3325        editor.change_selections(None, cx, |s| {
3326            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3327        });
3328        editor.move_line_down(&MoveLineDown, cx);
3329    });
3330}
3331
3332#[gpui::test]
3333fn test_transpose(cx: &mut TestAppContext) {
3334    init_test(cx, |_| {});
3335
3336    _ = cx.add_window(|cx| {
3337        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
3338        editor.set_style(EditorStyle::default(), cx);
3339        editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
3340        editor.transpose(&Default::default(), cx);
3341        assert_eq!(editor.text(cx), "bac");
3342        assert_eq!(editor.selections.ranges(cx), [2..2]);
3343
3344        editor.transpose(&Default::default(), cx);
3345        assert_eq!(editor.text(cx), "bca");
3346        assert_eq!(editor.selections.ranges(cx), [3..3]);
3347
3348        editor.transpose(&Default::default(), cx);
3349        assert_eq!(editor.text(cx), "bac");
3350        assert_eq!(editor.selections.ranges(cx), [3..3]);
3351
3352        editor
3353    });
3354
3355    _ = cx.add_window(|cx| {
3356        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3357        editor.set_style(EditorStyle::default(), cx);
3358        editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
3359        editor.transpose(&Default::default(), cx);
3360        assert_eq!(editor.text(cx), "acb\nde");
3361        assert_eq!(editor.selections.ranges(cx), [3..3]);
3362
3363        editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3364        editor.transpose(&Default::default(), cx);
3365        assert_eq!(editor.text(cx), "acbd\ne");
3366        assert_eq!(editor.selections.ranges(cx), [5..5]);
3367
3368        editor.transpose(&Default::default(), cx);
3369        assert_eq!(editor.text(cx), "acbde\n");
3370        assert_eq!(editor.selections.ranges(cx), [6..6]);
3371
3372        editor.transpose(&Default::default(), cx);
3373        assert_eq!(editor.text(cx), "acbd\ne");
3374        assert_eq!(editor.selections.ranges(cx), [6..6]);
3375
3376        editor
3377    });
3378
3379    _ = cx.add_window(|cx| {
3380        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3381        editor.set_style(EditorStyle::default(), cx);
3382        editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
3383        editor.transpose(&Default::default(), cx);
3384        assert_eq!(editor.text(cx), "bacd\ne");
3385        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
3386
3387        editor.transpose(&Default::default(), cx);
3388        assert_eq!(editor.text(cx), "bcade\n");
3389        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
3390
3391        editor.transpose(&Default::default(), cx);
3392        assert_eq!(editor.text(cx), "bcda\ne");
3393        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3394
3395        editor.transpose(&Default::default(), cx);
3396        assert_eq!(editor.text(cx), "bcade\n");
3397        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3398
3399        editor.transpose(&Default::default(), cx);
3400        assert_eq!(editor.text(cx), "bcaed\n");
3401        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
3402
3403        editor
3404    });
3405
3406    _ = cx.add_window(|cx| {
3407        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
3408        editor.set_style(EditorStyle::default(), cx);
3409        editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3410        editor.transpose(&Default::default(), cx);
3411        assert_eq!(editor.text(cx), "🏀🍐✋");
3412        assert_eq!(editor.selections.ranges(cx), [8..8]);
3413
3414        editor.transpose(&Default::default(), cx);
3415        assert_eq!(editor.text(cx), "🏀✋🍐");
3416        assert_eq!(editor.selections.ranges(cx), [11..11]);
3417
3418        editor.transpose(&Default::default(), cx);
3419        assert_eq!(editor.text(cx), "🏀🍐✋");
3420        assert_eq!(editor.selections.ranges(cx), [11..11]);
3421
3422        editor
3423    });
3424}
3425
3426#[gpui::test]
3427async fn test_clipboard(cx: &mut gpui::TestAppContext) {
3428    init_test(cx, |_| {});
3429
3430    let mut cx = EditorTestContext::new(cx).await;
3431
3432    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
3433    cx.update_editor(|e, cx| e.cut(&Cut, cx));
3434    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
3435
3436    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
3437    cx.set_state("two ˇfour ˇsix ˇ");
3438    cx.update_editor(|e, cx| e.paste(&Paste, cx));
3439    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
3440
3441    // Paste again but with only two cursors. Since the number of cursors doesn't
3442    // match the number of slices in the clipboard, the entire clipboard text
3443    // is pasted at each cursor.
3444    cx.set_state("ˇtwo one✅ four three six five ˇ");
3445    cx.update_editor(|e, cx| {
3446        e.handle_input("( ", cx);
3447        e.paste(&Paste, cx);
3448        e.handle_input(") ", cx);
3449    });
3450    cx.assert_editor_state(
3451        &([
3452            "( one✅ ",
3453            "three ",
3454            "five ) ˇtwo one✅ four three six five ( one✅ ",
3455            "three ",
3456            "five ) ˇ",
3457        ]
3458        .join("\n")),
3459    );
3460
3461    // Cut with three selections, one of which is full-line.
3462    cx.set_state(indoc! {"
3463        1«2ˇ»3
3464        4ˇ567
3465        «8ˇ»9"});
3466    cx.update_editor(|e, cx| e.cut(&Cut, cx));
3467    cx.assert_editor_state(indoc! {"
3468        1ˇ3
3469        ˇ9"});
3470
3471    // Paste with three selections, noticing how the copied selection that was full-line
3472    // gets inserted before the second cursor.
3473    cx.set_state(indoc! {"
3474        1ˇ3
34753476        «oˇ»ne"});
3477    cx.update_editor(|e, cx| e.paste(&Paste, cx));
3478    cx.assert_editor_state(indoc! {"
3479        12ˇ3
3480        4567
34813482        8ˇne"});
3483
3484    // Copy with a single cursor only, which writes the whole line into the clipboard.
3485    cx.set_state(indoc! {"
3486        The quick brown
3487        fox juˇmps over
3488        the lazy dog"});
3489    cx.update_editor(|e, cx| e.copy(&Copy, cx));
3490    assert_eq!(
3491        cx.read_from_clipboard().map(|item| item.text().to_owned()),
3492        Some("fox jumps over\n".to_owned())
3493    );
3494
3495    // Paste with three selections, noticing how the copied full-line selection is inserted
3496    // before the empty selections but replaces the selection that is non-empty.
3497    cx.set_state(indoc! {"
3498        Tˇhe quick brown
3499        «foˇ»x jumps over
3500        tˇhe lazy dog"});
3501    cx.update_editor(|e, cx| e.paste(&Paste, cx));
3502    cx.assert_editor_state(indoc! {"
3503        fox jumps over
3504        Tˇhe quick brown
3505        fox jumps over
3506        ˇx jumps over
3507        fox jumps over
3508        tˇhe lazy dog"});
3509}
3510
3511#[gpui::test]
3512async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
3513    init_test(cx, |_| {});
3514
3515    let mut cx = EditorTestContext::new(cx).await;
3516    let language = Arc::new(Language::new(
3517        LanguageConfig::default(),
3518        Some(tree_sitter_rust::language()),
3519    ));
3520    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
3521
3522    // Cut an indented block, without the leading whitespace.
3523    cx.set_state(indoc! {"
3524        const a: B = (
3525            c(),
3526            «d(
3527                e,
3528                f
3529            )ˇ»
3530        );
3531    "});
3532    cx.update_editor(|e, cx| e.cut(&Cut, cx));
3533    cx.assert_editor_state(indoc! {"
3534        const a: B = (
3535            c(),
3536            ˇ
3537        );
3538    "});
3539
3540    // Paste it at the same position.
3541    cx.update_editor(|e, cx| e.paste(&Paste, cx));
3542    cx.assert_editor_state(indoc! {"
3543        const a: B = (
3544            c(),
3545            d(
3546                e,
3547                f
35483549        );
3550    "});
3551
3552    // Paste it at a line with a lower indent level.
3553    cx.set_state(indoc! {"
3554        ˇ
3555        const a: B = (
3556            c(),
3557        );
3558    "});
3559    cx.update_editor(|e, cx| e.paste(&Paste, cx));
3560    cx.assert_editor_state(indoc! {"
3561        d(
3562            e,
3563            f
35643565        const a: B = (
3566            c(),
3567        );
3568    "});
3569
3570    // Cut an indented block, with the leading whitespace.
3571    cx.set_state(indoc! {"
3572        const a: B = (
3573            c(),
3574        «    d(
3575                e,
3576                f
3577            )
3578        ˇ»);
3579    "});
3580    cx.update_editor(|e, cx| e.cut(&Cut, cx));
3581    cx.assert_editor_state(indoc! {"
3582        const a: B = (
3583            c(),
3584        ˇ);
3585    "});
3586
3587    // Paste it at the same position.
3588    cx.update_editor(|e, cx| e.paste(&Paste, cx));
3589    cx.assert_editor_state(indoc! {"
3590        const a: B = (
3591            c(),
3592            d(
3593                e,
3594                f
3595            )
3596        ˇ);
3597    "});
3598
3599    // Paste it at a line with a higher indent level.
3600    cx.set_state(indoc! {"
3601        const a: B = (
3602            c(),
3603            d(
3604                e,
36053606            )
3607        );
3608    "});
3609    cx.update_editor(|e, cx| e.paste(&Paste, cx));
3610    cx.assert_editor_state(indoc! {"
3611        const a: B = (
3612            c(),
3613            d(
3614                e,
3615                f    d(
3616                    e,
3617                    f
3618                )
3619        ˇ
3620            )
3621        );
3622    "});
3623}
3624
3625#[gpui::test]
3626fn test_select_all(cx: &mut TestAppContext) {
3627    init_test(cx, |_| {});
3628
3629    let view = cx.add_window(|cx| {
3630        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
3631        build_editor(buffer, cx)
3632    });
3633    _ = view.update(cx, |view, cx| {
3634        view.select_all(&SelectAll, cx);
3635        assert_eq!(
3636            view.selections.display_ranges(cx),
3637            &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
3638        );
3639    });
3640}
3641
3642#[gpui::test]
3643fn test_select_line(cx: &mut TestAppContext) {
3644    init_test(cx, |_| {});
3645
3646    let view = cx.add_window(|cx| {
3647        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
3648        build_editor(buffer, cx)
3649    });
3650    _ = view.update(cx, |view, cx| {
3651        view.change_selections(None, cx, |s| {
3652            s.select_display_ranges([
3653                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3654                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3655                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
3656                DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
3657            ])
3658        });
3659        view.select_line(&SelectLine, cx);
3660        assert_eq!(
3661            view.selections.display_ranges(cx),
3662            vec![
3663                DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
3664                DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
3665            ]
3666        );
3667    });
3668
3669    _ = view.update(cx, |view, cx| {
3670        view.select_line(&SelectLine, cx);
3671        assert_eq!(
3672            view.selections.display_ranges(cx),
3673            vec![
3674                DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
3675                DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
3676            ]
3677        );
3678    });
3679
3680    _ = view.update(cx, |view, cx| {
3681        view.select_line(&SelectLine, cx);
3682        assert_eq!(
3683            view.selections.display_ranges(cx),
3684            vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
3685        );
3686    });
3687}
3688
3689#[gpui::test]
3690fn test_split_selection_into_lines(cx: &mut TestAppContext) {
3691    init_test(cx, |_| {});
3692
3693    let view = cx.add_window(|cx| {
3694        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
3695        build_editor(buffer, cx)
3696    });
3697    _ = view.update(cx, |view, cx| {
3698        view.fold_ranges(
3699            vec![
3700                Point::new(0, 2)..Point::new(1, 2),
3701                Point::new(2, 3)..Point::new(4, 1),
3702                Point::new(7, 0)..Point::new(8, 4),
3703            ],
3704            true,
3705            cx,
3706        );
3707        view.change_selections(None, cx, |s| {
3708            s.select_display_ranges([
3709                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3710                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3711                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
3712                DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
3713            ])
3714        });
3715        assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
3716    });
3717
3718    _ = view.update(cx, |view, cx| {
3719        view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
3720        assert_eq!(
3721            view.display_text(cx),
3722            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
3723        );
3724        assert_eq!(
3725            view.selections.display_ranges(cx),
3726            [
3727                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
3728                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3729                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
3730                DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
3731            ]
3732        );
3733    });
3734
3735    _ = view.update(cx, |view, cx| {
3736        view.change_selections(None, cx, |s| {
3737            s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
3738        });
3739        view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
3740        assert_eq!(
3741            view.display_text(cx),
3742            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
3743        );
3744        assert_eq!(
3745            view.selections.display_ranges(cx),
3746            [
3747                DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
3748                DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
3749                DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
3750                DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
3751                DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
3752                DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
3753                DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
3754                DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
3755            ]
3756        );
3757    });
3758}
3759
3760#[gpui::test]
3761async fn test_add_selection_above_below(cx: &mut TestAppContext) {
3762    init_test(cx, |_| {});
3763
3764    let mut cx = EditorTestContext::new(cx).await;
3765
3766    // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
3767    cx.set_state(indoc!(
3768        r#"abc
3769           defˇghi
3770
3771           jk
3772           nlmo
3773           "#
3774    ));
3775
3776    cx.update_editor(|editor, cx| {
3777        editor.add_selection_above(&Default::default(), cx);
3778    });
3779
3780    cx.assert_editor_state(indoc!(
3781        r#"abcˇ
3782           defˇghi
3783
3784           jk
3785           nlmo
3786           "#
3787    ));
3788
3789    cx.update_editor(|editor, cx| {
3790        editor.add_selection_above(&Default::default(), cx);
3791    });
3792
3793    cx.assert_editor_state(indoc!(
3794        r#"abcˇ
3795            defˇghi
3796
3797            jk
3798            nlmo
3799            "#
3800    ));
3801
3802    cx.update_editor(|view, cx| {
3803        view.add_selection_below(&Default::default(), cx);
3804    });
3805
3806    cx.assert_editor_state(indoc!(
3807        r#"abc
3808           defˇghi
3809
3810           jk
3811           nlmo
3812           "#
3813    ));
3814
3815    cx.update_editor(|view, cx| {
3816        view.undo_selection(&Default::default(), cx);
3817    });
3818
3819    cx.assert_editor_state(indoc!(
3820        r#"abcˇ
3821           defˇghi
3822
3823           jk
3824           nlmo
3825           "#
3826    ));
3827
3828    cx.update_editor(|view, cx| {
3829        view.redo_selection(&Default::default(), cx);
3830    });
3831
3832    cx.assert_editor_state(indoc!(
3833        r#"abc
3834           defˇghi
3835
3836           jk
3837           nlmo
3838           "#
3839    ));
3840
3841    cx.update_editor(|view, cx| {
3842        view.add_selection_below(&Default::default(), cx);
3843    });
3844
3845    cx.assert_editor_state(indoc!(
3846        r#"abc
3847           defˇghi
3848
3849           jk
3850           nlmˇo
3851           "#
3852    ));
3853
3854    cx.update_editor(|view, cx| {
3855        view.add_selection_below(&Default::default(), cx);
3856    });
3857
3858    cx.assert_editor_state(indoc!(
3859        r#"abc
3860           defˇghi
3861
3862           jk
3863           nlmˇo
3864           "#
3865    ));
3866
3867    // change selections
3868    cx.set_state(indoc!(
3869        r#"abc
3870           def«ˇg»hi
3871
3872           jk
3873           nlmo
3874           "#
3875    ));
3876
3877    cx.update_editor(|view, cx| {
3878        view.add_selection_below(&Default::default(), cx);
3879    });
3880
3881    cx.assert_editor_state(indoc!(
3882        r#"abc
3883           def«ˇg»hi
3884
3885           jk
3886           nlm«ˇo»
3887           "#
3888    ));
3889
3890    cx.update_editor(|view, cx| {
3891        view.add_selection_below(&Default::default(), cx);
3892    });
3893
3894    cx.assert_editor_state(indoc!(
3895        r#"abc
3896           def«ˇg»hi
3897
3898           jk
3899           nlm«ˇo»
3900           "#
3901    ));
3902
3903    cx.update_editor(|view, cx| {
3904        view.add_selection_above(&Default::default(), cx);
3905    });
3906
3907    cx.assert_editor_state(indoc!(
3908        r#"abc
3909           def«ˇg»hi
3910
3911           jk
3912           nlmo
3913           "#
3914    ));
3915
3916    cx.update_editor(|view, cx| {
3917        view.add_selection_above(&Default::default(), cx);
3918    });
3919
3920    cx.assert_editor_state(indoc!(
3921        r#"abc
3922           def«ˇg»hi
3923
3924           jk
3925           nlmo
3926           "#
3927    ));
3928
3929    // Change selections again
3930    cx.set_state(indoc!(
3931        r#"a«bc
3932           defgˇ»hi
3933
3934           jk
3935           nlmo
3936           "#
3937    ));
3938
3939    cx.update_editor(|view, cx| {
3940        view.add_selection_below(&Default::default(), cx);
3941    });
3942
3943    cx.assert_editor_state(indoc!(
3944        r#"a«bcˇ»
3945           d«efgˇ»hi
3946
3947           j«kˇ»
3948           nlmo
3949           "#
3950    ));
3951
3952    cx.update_editor(|view, cx| {
3953        view.add_selection_below(&Default::default(), cx);
3954    });
3955    cx.assert_editor_state(indoc!(
3956        r#"a«bcˇ»
3957           d«efgˇ»hi
3958
3959           j«kˇ»
3960           n«lmoˇ»
3961           "#
3962    ));
3963    cx.update_editor(|view, cx| {
3964        view.add_selection_above(&Default::default(), cx);
3965    });
3966
3967    cx.assert_editor_state(indoc!(
3968        r#"a«bcˇ»
3969           d«efgˇ»hi
3970
3971           j«kˇ»
3972           nlmo
3973           "#
3974    ));
3975
3976    // Change selections again
3977    cx.set_state(indoc!(
3978        r#"abc
3979           d«ˇefghi
3980
3981           jk
3982           nlm»o
3983           "#
3984    ));
3985
3986    cx.update_editor(|view, cx| {
3987        view.add_selection_above(&Default::default(), cx);
3988    });
3989
3990    cx.assert_editor_state(indoc!(
3991        r#"a«ˇbc»
3992           d«ˇef»ghi
3993
3994           j«ˇk»
3995           n«ˇlm»o
3996           "#
3997    ));
3998
3999    cx.update_editor(|view, cx| {
4000        view.add_selection_below(&Default::default(), cx);
4001    });
4002
4003    cx.assert_editor_state(indoc!(
4004        r#"abc
4005           d«ˇef»ghi
4006
4007           j«ˇk»
4008           n«ˇlm»o
4009           "#
4010    ));
4011}
4012
4013#[gpui::test]
4014async fn test_select_next(cx: &mut gpui::TestAppContext) {
4015    init_test(cx, |_| {});
4016
4017    let mut cx = EditorTestContext::new(cx).await;
4018    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4019
4020    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4021        .unwrap();
4022    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4023
4024    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4025        .unwrap();
4026    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4027
4028    cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4029    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4030
4031    cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4032    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4033
4034    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4035        .unwrap();
4036    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4037
4038    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4039        .unwrap();
4040    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4041}
4042
4043#[gpui::test]
4044async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
4045    init_test(cx, |_| {});
4046
4047    let mut cx = EditorTestContext::new(cx).await;
4048    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4049
4050    cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
4051        .unwrap();
4052    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4053}
4054
4055#[gpui::test]
4056async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4057    init_test(cx, |_| {});
4058
4059    let mut cx = EditorTestContext::new(cx).await;
4060    cx.set_state(
4061        r#"let foo = 2;
4062lˇet foo = 2;
4063let fooˇ = 2;
4064let foo = 2;
4065let foo = ˇ2;"#,
4066    );
4067
4068    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4069        .unwrap();
4070    cx.assert_editor_state(
4071        r#"let foo = 2;
4072«letˇ» foo = 2;
4073let «fooˇ» = 2;
4074let foo = 2;
4075let foo = «2ˇ»;"#,
4076    );
4077
4078    // noop for multiple selections with different contents
4079    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4080        .unwrap();
4081    cx.assert_editor_state(
4082        r#"let foo = 2;
4083«letˇ» foo = 2;
4084let «fooˇ» = 2;
4085let foo = 2;
4086let foo = «2ˇ»;"#,
4087    );
4088}
4089
4090#[gpui::test]
4091async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
4092    init_test(cx, |_| {});
4093
4094    let mut cx = EditorTestContext::new(cx).await;
4095    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4096
4097    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4098        .unwrap();
4099    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4100
4101    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4102        .unwrap();
4103    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4104
4105    cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4106    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4107
4108    cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4109    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4110
4111    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4112        .unwrap();
4113    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
4114
4115    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4116        .unwrap();
4117    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
4118
4119    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4120        .unwrap();
4121    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4122}
4123
4124#[gpui::test]
4125async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4126    init_test(cx, |_| {});
4127
4128    let mut cx = EditorTestContext::new(cx).await;
4129    cx.set_state(
4130        r#"let foo = 2;
4131lˇet foo = 2;
4132let fooˇ = 2;
4133let foo = 2;
4134let foo = ˇ2;"#,
4135    );
4136
4137    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4138        .unwrap();
4139    cx.assert_editor_state(
4140        r#"let foo = 2;
4141«letˇ» foo = 2;
4142let «fooˇ» = 2;
4143let foo = 2;
4144let foo = «2ˇ»;"#,
4145    );
4146
4147    // noop for multiple selections with different contents
4148    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4149        .unwrap();
4150    cx.assert_editor_state(
4151        r#"let foo = 2;
4152«letˇ» foo = 2;
4153let «fooˇ» = 2;
4154let foo = 2;
4155let foo = «2ˇ»;"#,
4156    );
4157}
4158
4159#[gpui::test]
4160async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
4161    init_test(cx, |_| {});
4162
4163    let mut cx = EditorTestContext::new(cx).await;
4164    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
4165
4166    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4167        .unwrap();
4168    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4169
4170    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4171        .unwrap();
4172    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4173
4174    cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4175    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4176
4177    cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4178    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4179
4180    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4181        .unwrap();
4182    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
4183
4184    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4185        .unwrap();
4186    cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4187}
4188
4189#[gpui::test]
4190async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
4191    init_test(cx, |_| {});
4192
4193    let language = Arc::new(Language::new(
4194        LanguageConfig::default(),
4195        Some(tree_sitter_rust::language()),
4196    ));
4197
4198    let text = r#"
4199        use mod1::mod2::{mod3, mod4};
4200
4201        fn fn_1(param1: bool, param2: &str) {
4202            let var1 = "text";
4203        }
4204    "#
4205    .unindent();
4206
4207    let buffer = cx.new_model(|cx| {
4208        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
4209            .with_language(language, cx)
4210    });
4211    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4212    let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4213
4214    view.condition::<crate::EditorEvent>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4215        .await;
4216
4217    _ = view.update(cx, |view, cx| {
4218        view.change_selections(None, cx, |s| {
4219            s.select_display_ranges([
4220                DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
4221                DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
4222                DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
4223            ]);
4224        });
4225        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4226    });
4227    assert_eq!(
4228        view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
4229        &[
4230            DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
4231            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
4232            DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
4233        ]
4234    );
4235
4236    _ = view.update(cx, |view, cx| {
4237        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4238    });
4239    assert_eq!(
4240        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4241        &[
4242            DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
4243            DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
4244        ]
4245    );
4246
4247    _ = view.update(cx, |view, cx| {
4248        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4249    });
4250    assert_eq!(
4251        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4252        &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
4253    );
4254
4255    // Trying to expand the selected syntax node one more time has no effect.
4256    _ = view.update(cx, |view, cx| {
4257        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4258    });
4259    assert_eq!(
4260        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4261        &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
4262    );
4263
4264    _ = view.update(cx, |view, cx| {
4265        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4266    });
4267    assert_eq!(
4268        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4269        &[
4270            DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
4271            DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
4272        ]
4273    );
4274
4275    _ = view.update(cx, |view, cx| {
4276        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4277    });
4278    assert_eq!(
4279        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4280        &[
4281            DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
4282            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
4283            DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
4284        ]
4285    );
4286
4287    _ = view.update(cx, |view, cx| {
4288        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4289    });
4290    assert_eq!(
4291        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4292        &[
4293            DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
4294            DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
4295            DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
4296        ]
4297    );
4298
4299    // Trying to shrink the selected syntax node one more time has no effect.
4300    _ = view.update(cx, |view, cx| {
4301        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4302    });
4303    assert_eq!(
4304        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4305        &[
4306            DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
4307            DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
4308            DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
4309        ]
4310    );
4311
4312    // Ensure that we keep expanding the selection if the larger selection starts or ends within
4313    // a fold.
4314    _ = view.update(cx, |view, cx| {
4315        view.fold_ranges(
4316            vec![
4317                Point::new(0, 21)..Point::new(0, 24),
4318                Point::new(3, 20)..Point::new(3, 22),
4319            ],
4320            true,
4321            cx,
4322        );
4323        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4324    });
4325    assert_eq!(
4326        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4327        &[
4328            DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
4329            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
4330            DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
4331        ]
4332    );
4333}
4334
4335#[gpui::test]
4336async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
4337    init_test(cx, |_| {});
4338
4339    let language = Arc::new(
4340        Language::new(
4341            LanguageConfig {
4342                brackets: BracketPairConfig {
4343                    pairs: vec![
4344                        BracketPair {
4345                            start: "{".to_string(),
4346                            end: "}".to_string(),
4347                            close: false,
4348                            newline: true,
4349                        },
4350                        BracketPair {
4351                            start: "(".to_string(),
4352                            end: ")".to_string(),
4353                            close: false,
4354                            newline: true,
4355                        },
4356                    ],
4357                    ..Default::default()
4358                },
4359                ..Default::default()
4360            },
4361            Some(tree_sitter_rust::language()),
4362        )
4363        .with_indents_query(
4364            r#"
4365                (_ "(" ")" @end) @indent
4366                (_ "{" "}" @end) @indent
4367            "#,
4368        )
4369        .unwrap(),
4370    );
4371
4372    let text = "fn a() {}";
4373
4374    let buffer = cx.new_model(|cx| {
4375        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
4376            .with_language(language, cx)
4377    });
4378    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4379    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4380    editor
4381        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
4382        .await;
4383
4384    _ = editor.update(cx, |editor, cx| {
4385        editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
4386        editor.newline(&Newline, cx);
4387        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
4388        assert_eq!(
4389            editor.selections.ranges(cx),
4390            &[
4391                Point::new(1, 4)..Point::new(1, 4),
4392                Point::new(3, 4)..Point::new(3, 4),
4393                Point::new(5, 0)..Point::new(5, 0)
4394            ]
4395        );
4396    });
4397}
4398
4399#[gpui::test]
4400async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
4401    init_test(cx, |_| {});
4402
4403    let mut cx = EditorTestContext::new(cx).await;
4404
4405    let language = Arc::new(Language::new(
4406        LanguageConfig {
4407            brackets: BracketPairConfig {
4408                pairs: vec![
4409                    BracketPair {
4410                        start: "{".to_string(),
4411                        end: "}".to_string(),
4412                        close: true,
4413                        newline: true,
4414                    },
4415                    BracketPair {
4416                        start: "(".to_string(),
4417                        end: ")".to_string(),
4418                        close: true,
4419                        newline: true,
4420                    },
4421                    BracketPair {
4422                        start: "/*".to_string(),
4423                        end: " */".to_string(),
4424                        close: true,
4425                        newline: true,
4426                    },
4427                    BracketPair {
4428                        start: "[".to_string(),
4429                        end: "]".to_string(),
4430                        close: false,
4431                        newline: true,
4432                    },
4433                    BracketPair {
4434                        start: "\"".to_string(),
4435                        end: "\"".to_string(),
4436                        close: true,
4437                        newline: false,
4438                    },
4439                ],
4440                ..Default::default()
4441            },
4442            autoclose_before: "})]".to_string(),
4443            ..Default::default()
4444        },
4445        Some(tree_sitter_rust::language()),
4446    ));
4447
4448    cx.language_registry().add(language.clone());
4449    cx.update_buffer(|buffer, cx| {
4450        buffer.set_language(Some(language), cx);
4451    });
4452
4453    cx.set_state(
4454        &r#"
4455            🏀ˇ
4456            εˇ
4457            ❤️ˇ
4458        "#
4459        .unindent(),
4460    );
4461
4462    // autoclose multiple nested brackets at multiple cursors
4463    cx.update_editor(|view, cx| {
4464        view.handle_input("{", cx);
4465        view.handle_input("{", cx);
4466        view.handle_input("{", cx);
4467    });
4468    cx.assert_editor_state(
4469        &"
4470            🏀{{{ˇ}}}
4471            ε{{{ˇ}}}
4472            ❤️{{{ˇ}}}
4473        "
4474        .unindent(),
4475    );
4476
4477    // insert a different closing bracket
4478    cx.update_editor(|view, cx| {
4479        view.handle_input(")", cx);
4480    });
4481    cx.assert_editor_state(
4482        &"
4483            🏀{{{)ˇ}}}
4484            ε{{{)ˇ}}}
4485            ❤️{{{)ˇ}}}
4486        "
4487        .unindent(),
4488    );
4489
4490    // skip over the auto-closed brackets when typing a closing bracket
4491    cx.update_editor(|view, cx| {
4492        view.move_right(&MoveRight, cx);
4493        view.handle_input("}", cx);
4494        view.handle_input("}", cx);
4495        view.handle_input("}", cx);
4496    });
4497    cx.assert_editor_state(
4498        &"
4499            🏀{{{)}}}}ˇ
4500            ε{{{)}}}}ˇ
4501            ❤️{{{)}}}}ˇ
4502        "
4503        .unindent(),
4504    );
4505
4506    // autoclose multi-character pairs
4507    cx.set_state(
4508        &"
4509            ˇ
4510            ˇ
4511        "
4512        .unindent(),
4513    );
4514    cx.update_editor(|view, cx| {
4515        view.handle_input("/", cx);
4516        view.handle_input("*", cx);
4517    });
4518    cx.assert_editor_state(
4519        &"
4520            /*ˇ */
4521            /*ˇ */
4522        "
4523        .unindent(),
4524    );
4525
4526    // one cursor autocloses a multi-character pair, one cursor
4527    // does not autoclose.
4528    cx.set_state(
4529        &"
45304531            ˇ
4532        "
4533        .unindent(),
4534    );
4535    cx.update_editor(|view, cx| view.handle_input("*", cx));
4536    cx.assert_editor_state(
4537        &"
4538            /*ˇ */
45394540        "
4541        .unindent(),
4542    );
4543
4544    // Don't autoclose if the next character isn't whitespace and isn't
4545    // listed in the language's "autoclose_before" section.
4546    cx.set_state("ˇa b");
4547    cx.update_editor(|view, cx| view.handle_input("{", cx));
4548    cx.assert_editor_state("{ˇa b");
4549
4550    // Don't autoclose if `close` is false for the bracket pair
4551    cx.set_state("ˇ");
4552    cx.update_editor(|view, cx| view.handle_input("[", cx));
4553    cx.assert_editor_state("");
4554
4555    // Surround with brackets if text is selected
4556    cx.set_state("«aˇ» b");
4557    cx.update_editor(|view, cx| view.handle_input("{", cx));
4558    cx.assert_editor_state("{«aˇ»} b");
4559
4560    // Autclose pair where the start and end characters are the same
4561    cx.set_state("");
4562    cx.update_editor(|view, cx| view.handle_input("\"", cx));
4563    cx.assert_editor_state("a\"ˇ\"");
4564    cx.update_editor(|view, cx| view.handle_input("\"", cx));
4565    cx.assert_editor_state("a\"\"ˇ");
4566}
4567
4568#[gpui::test]
4569async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
4570    init_test(cx, |settings| {
4571        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
4572    });
4573
4574    let mut cx = EditorTestContext::new(cx).await;
4575
4576    let language = Arc::new(Language::new(
4577        LanguageConfig {
4578            brackets: BracketPairConfig {
4579                pairs: vec![
4580                    BracketPair {
4581                        start: "{".to_string(),
4582                        end: "}".to_string(),
4583                        close: true,
4584                        newline: true,
4585                    },
4586                    BracketPair {
4587                        start: "(".to_string(),
4588                        end: ")".to_string(),
4589                        close: true,
4590                        newline: true,
4591                    },
4592                    BracketPair {
4593                        start: "[".to_string(),
4594                        end: "]".to_string(),
4595                        close: false,
4596                        newline: true,
4597                    },
4598                ],
4599                ..Default::default()
4600            },
4601            autoclose_before: "})]".to_string(),
4602            ..Default::default()
4603        },
4604        Some(tree_sitter_rust::language()),
4605    ));
4606
4607    cx.language_registry().add(language.clone());
4608    cx.update_buffer(|buffer, cx| {
4609        buffer.set_language(Some(language), cx);
4610    });
4611
4612    cx.set_state(
4613        &"
4614            ˇ
4615            ˇ
4616            ˇ
4617        "
4618        .unindent(),
4619    );
4620
4621    // ensure only matching closing brackets are skipped over
4622    cx.update_editor(|view, cx| {
4623        view.handle_input("}", cx);
4624        view.move_left(&MoveLeft, cx);
4625        view.handle_input(")", cx);
4626        view.move_left(&MoveLeft, cx);
4627    });
4628    cx.assert_editor_state(
4629        &"
4630            ˇ)}
4631            ˇ)}
4632            ˇ)}
4633        "
4634        .unindent(),
4635    );
4636
4637    // skip-over closing brackets at multiple cursors
4638    cx.update_editor(|view, cx| {
4639        view.handle_input(")", cx);
4640        view.handle_input("}", cx);
4641    });
4642    cx.assert_editor_state(
4643        &"
4644            )}ˇ
4645            )}ˇ
4646            )}ˇ
4647        "
4648        .unindent(),
4649    );
4650
4651    // ignore non-close brackets
4652    cx.update_editor(|view, cx| {
4653        view.handle_input("]", cx);
4654        view.move_left(&MoveLeft, cx);
4655        view.handle_input("]", cx);
4656    });
4657    cx.assert_editor_state(
4658        &"
4659            )}]ˇ]
4660            )}]ˇ]
4661            )}]ˇ]
4662        "
4663        .unindent(),
4664    );
4665}
4666
4667#[gpui::test]
4668async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
4669    init_test(cx, |_| {});
4670
4671    let mut cx = EditorTestContext::new(cx).await;
4672
4673    let html_language = Arc::new(
4674        Language::new(
4675            LanguageConfig {
4676                name: "HTML".into(),
4677                brackets: BracketPairConfig {
4678                    pairs: vec![
4679                        BracketPair {
4680                            start: "<".into(),
4681                            end: ">".into(),
4682                            close: true,
4683                            ..Default::default()
4684                        },
4685                        BracketPair {
4686                            start: "{".into(),
4687                            end: "}".into(),
4688                            close: true,
4689                            ..Default::default()
4690                        },
4691                        BracketPair {
4692                            start: "(".into(),
4693                            end: ")".into(),
4694                            close: true,
4695                            ..Default::default()
4696                        },
4697                    ],
4698                    ..Default::default()
4699                },
4700                autoclose_before: "})]>".into(),
4701                ..Default::default()
4702            },
4703            Some(tree_sitter_html::language()),
4704        )
4705        .with_injection_query(
4706            r#"
4707            (script_element
4708                (raw_text) @content
4709                (#set! "language" "javascript"))
4710            "#,
4711        )
4712        .unwrap(),
4713    );
4714
4715    let javascript_language = Arc::new(Language::new(
4716        LanguageConfig {
4717            name: "JavaScript".into(),
4718            brackets: BracketPairConfig {
4719                pairs: vec![
4720                    BracketPair {
4721                        start: "/*".into(),
4722                        end: " */".into(),
4723                        close: true,
4724                        ..Default::default()
4725                    },
4726                    BracketPair {
4727                        start: "{".into(),
4728                        end: "}".into(),
4729                        close: true,
4730                        ..Default::default()
4731                    },
4732                    BracketPair {
4733                        start: "(".into(),
4734                        end: ")".into(),
4735                        close: true,
4736                        ..Default::default()
4737                    },
4738                ],
4739                ..Default::default()
4740            },
4741            autoclose_before: "})]>".into(),
4742            ..Default::default()
4743        },
4744        Some(tree_sitter_typescript::language_tsx()),
4745    ));
4746
4747    cx.language_registry().add(html_language.clone());
4748    cx.language_registry().add(javascript_language.clone());
4749
4750    cx.update_buffer(|buffer, cx| {
4751        buffer.set_language(Some(html_language), cx);
4752    });
4753
4754    cx.set_state(
4755        &r#"
4756            <body>ˇ
4757                <script>
4758                    var x = 1;ˇ
4759                </script>
4760            </body>ˇ
4761        "#
4762        .unindent(),
4763    );
4764
4765    // Precondition: different languages are active at different locations.
4766    cx.update_editor(|editor, cx| {
4767        let snapshot = editor.snapshot(cx);
4768        let cursors = editor.selections.ranges::<usize>(cx);
4769        let languages = cursors
4770            .iter()
4771            .map(|c| snapshot.language_at(c.start).unwrap().name())
4772            .collect::<Vec<_>>();
4773        assert_eq!(
4774            languages,
4775            &["HTML".into(), "JavaScript".into(), "HTML".into()]
4776        );
4777    });
4778
4779    // Angle brackets autoclose in HTML, but not JavaScript.
4780    cx.update_editor(|editor, cx| {
4781        editor.handle_input("<", cx);
4782        editor.handle_input("a", cx);
4783    });
4784    cx.assert_editor_state(
4785        &r#"
4786            <body><aˇ>
4787                <script>
4788                    var x = 1;<aˇ
4789                </script>
4790            </body><aˇ>
4791        "#
4792        .unindent(),
4793    );
4794
4795    // Curly braces and parens autoclose in both HTML and JavaScript.
4796    cx.update_editor(|editor, cx| {
4797        editor.handle_input(" b=", cx);
4798        editor.handle_input("{", cx);
4799        editor.handle_input("c", cx);
4800        editor.handle_input("(", cx);
4801    });
4802    cx.assert_editor_state(
4803        &r#"
4804            <body><a b={c(ˇ)}>
4805                <script>
4806                    var x = 1;<a b={c(ˇ)}
4807                </script>
4808            </body><a b={c(ˇ)}>
4809        "#
4810        .unindent(),
4811    );
4812
4813    // Brackets that were already autoclosed are skipped.
4814    cx.update_editor(|editor, cx| {
4815        editor.handle_input(")", cx);
4816        editor.handle_input("d", cx);
4817        editor.handle_input("}", cx);
4818    });
4819    cx.assert_editor_state(
4820        &r#"
4821            <body><a b={c()d}ˇ>
4822                <script>
4823                    var x = 1;<a b={c()d}ˇ
4824                </script>
4825            </body><a b={c()d}ˇ>
4826        "#
4827        .unindent(),
4828    );
4829    cx.update_editor(|editor, cx| {
4830        editor.handle_input(">", cx);
4831    });
4832    cx.assert_editor_state(
4833        &r#"
4834            <body><a b={c()d}>ˇ
4835                <script>
4836                    var x = 1;<a b={c()d}>ˇ
4837                </script>
4838            </body><a b={c()d}>ˇ
4839        "#
4840        .unindent(),
4841    );
4842
4843    // Reset
4844    cx.set_state(
4845        &r#"
4846            <body>ˇ
4847                <script>
4848                    var x = 1;ˇ
4849                </script>
4850            </body>ˇ
4851        "#
4852        .unindent(),
4853    );
4854
4855    cx.update_editor(|editor, cx| {
4856        editor.handle_input("<", cx);
4857    });
4858    cx.assert_editor_state(
4859        &r#"
4860            <body><ˇ>
4861                <script>
4862                    var x = 1;<ˇ
4863                </script>
4864            </body><ˇ>
4865        "#
4866        .unindent(),
4867    );
4868
4869    // When backspacing, the closing angle brackets are removed.
4870    cx.update_editor(|editor, cx| {
4871        editor.backspace(&Backspace, cx);
4872    });
4873    cx.assert_editor_state(
4874        &r#"
4875            <body>ˇ
4876                <script>
4877                    var x = 1;ˇ
4878                </script>
4879            </body>ˇ
4880        "#
4881        .unindent(),
4882    );
4883
4884    // Block comments autoclose in JavaScript, but not HTML.
4885    cx.update_editor(|editor, cx| {
4886        editor.handle_input("/", cx);
4887        editor.handle_input("*", cx);
4888    });
4889    cx.assert_editor_state(
4890        &r#"
4891            <body>/*ˇ
4892                <script>
4893                    var x = 1;/*ˇ */
4894                </script>
4895            </body>/*ˇ
4896        "#
4897        .unindent(),
4898    );
4899}
4900
4901#[gpui::test]
4902async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
4903    init_test(cx, |_| {});
4904
4905    let mut cx = EditorTestContext::new(cx).await;
4906
4907    let rust_language = Arc::new(
4908        Language::new(
4909            LanguageConfig {
4910                name: "Rust".into(),
4911                brackets: serde_json::from_value(json!([
4912                    { "start": "{", "end": "}", "close": true, "newline": true },
4913                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
4914                ]))
4915                .unwrap(),
4916                autoclose_before: "})]>".into(),
4917                ..Default::default()
4918            },
4919            Some(tree_sitter_rust::language()),
4920        )
4921        .with_override_query("(string_literal) @string")
4922        .unwrap(),
4923    );
4924
4925    cx.language_registry().add(rust_language.clone());
4926    cx.update_buffer(|buffer, cx| {
4927        buffer.set_language(Some(rust_language), cx);
4928    });
4929
4930    cx.set_state(
4931        &r#"
4932            let x = ˇ
4933        "#
4934        .unindent(),
4935    );
4936
4937    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
4938    cx.update_editor(|editor, cx| {
4939        editor.handle_input("\"", cx);
4940    });
4941    cx.assert_editor_state(
4942        &r#"
4943            let x = "ˇ"
4944        "#
4945        .unindent(),
4946    );
4947
4948    // Inserting another quotation mark. The cursor moves across the existing
4949    // automatically-inserted quotation mark.
4950    cx.update_editor(|editor, cx| {
4951        editor.handle_input("\"", cx);
4952    });
4953    cx.assert_editor_state(
4954        &r#"
4955            let x = ""ˇ
4956        "#
4957        .unindent(),
4958    );
4959
4960    // Reset
4961    cx.set_state(
4962        &r#"
4963            let x = ˇ
4964        "#
4965        .unindent(),
4966    );
4967
4968    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
4969    cx.update_editor(|editor, cx| {
4970        editor.handle_input("\"", cx);
4971        editor.handle_input(" ", cx);
4972        editor.move_left(&Default::default(), cx);
4973        editor.handle_input("\\", cx);
4974        editor.handle_input("\"", cx);
4975    });
4976    cx.assert_editor_state(
4977        &r#"
4978            let x = "\"ˇ "
4979        "#
4980        .unindent(),
4981    );
4982
4983    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
4984    // mark. Nothing is inserted.
4985    cx.update_editor(|editor, cx| {
4986        editor.move_right(&Default::default(), cx);
4987        editor.handle_input("\"", cx);
4988    });
4989    cx.assert_editor_state(
4990        &r#"
4991            let x = "\" "ˇ
4992        "#
4993        .unindent(),
4994    );
4995}
4996
4997#[gpui::test]
4998async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
4999    init_test(cx, |_| {});
5000
5001    let language = Arc::new(Language::new(
5002        LanguageConfig {
5003            brackets: BracketPairConfig {
5004                pairs: vec![
5005                    BracketPair {
5006                        start: "{".to_string(),
5007                        end: "}".to_string(),
5008                        close: true,
5009                        newline: true,
5010                    },
5011                    BracketPair {
5012                        start: "/* ".to_string(),
5013                        end: "*/".to_string(),
5014                        close: true,
5015                        ..Default::default()
5016                    },
5017                ],
5018                ..Default::default()
5019            },
5020            ..Default::default()
5021        },
5022        Some(tree_sitter_rust::language()),
5023    ));
5024
5025    let text = r#"
5026        a
5027        b
5028        c
5029    "#
5030    .unindent();
5031
5032    let buffer = cx.new_model(|cx| {
5033        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
5034            .with_language(language, cx)
5035    });
5036    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5037    let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5038    view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5039        .await;
5040
5041    _ = view.update(cx, |view, cx| {
5042        view.change_selections(None, cx, |s| {
5043            s.select_display_ranges([
5044                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
5045                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
5046                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
5047            ])
5048        });
5049
5050        view.handle_input("{", cx);
5051        view.handle_input("{", cx);
5052        view.handle_input("{", cx);
5053        assert_eq!(
5054            view.text(cx),
5055            "
5056                {{{a}}}
5057                {{{b}}}
5058                {{{c}}}
5059            "
5060            .unindent()
5061        );
5062        assert_eq!(
5063            view.selections.display_ranges(cx),
5064            [
5065                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
5066                DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
5067                DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
5068            ]
5069        );
5070
5071        view.undo(&Undo, cx);
5072        view.undo(&Undo, cx);
5073        view.undo(&Undo, cx);
5074        assert_eq!(
5075            view.text(cx),
5076            "
5077                a
5078                b
5079                c
5080            "
5081            .unindent()
5082        );
5083        assert_eq!(
5084            view.selections.display_ranges(cx),
5085            [
5086                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
5087                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
5088                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
5089            ]
5090        );
5091
5092        // Ensure inserting the first character of a multi-byte bracket pair
5093        // doesn't surround the selections with the bracket.
5094        view.handle_input("/", cx);
5095        assert_eq!(
5096            view.text(cx),
5097            "
5098                /
5099                /
5100                /
5101            "
5102            .unindent()
5103        );
5104        assert_eq!(
5105            view.selections.display_ranges(cx),
5106            [
5107                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
5108                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
5109                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
5110            ]
5111        );
5112
5113        view.undo(&Undo, cx);
5114        assert_eq!(
5115            view.text(cx),
5116            "
5117                a
5118                b
5119                c
5120            "
5121            .unindent()
5122        );
5123        assert_eq!(
5124            view.selections.display_ranges(cx),
5125            [
5126                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
5127                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
5128                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
5129            ]
5130        );
5131
5132        // Ensure inserting the last character of a multi-byte bracket pair
5133        // doesn't surround the selections with the bracket.
5134        view.handle_input("*", cx);
5135        assert_eq!(
5136            view.text(cx),
5137            "
5138                *
5139                *
5140                *
5141            "
5142            .unindent()
5143        );
5144        assert_eq!(
5145            view.selections.display_ranges(cx),
5146            [
5147                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
5148                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
5149                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
5150            ]
5151        );
5152    });
5153}
5154
5155#[gpui::test]
5156async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
5157    init_test(cx, |_| {});
5158
5159    let language = Arc::new(Language::new(
5160        LanguageConfig {
5161            brackets: BracketPairConfig {
5162                pairs: vec![BracketPair {
5163                    start: "{".to_string(),
5164                    end: "}".to_string(),
5165                    close: true,
5166                    newline: true,
5167                }],
5168                ..Default::default()
5169            },
5170            autoclose_before: "}".to_string(),
5171            ..Default::default()
5172        },
5173        Some(tree_sitter_rust::language()),
5174    ));
5175
5176    let text = r#"
5177        a
5178        b
5179        c
5180    "#
5181    .unindent();
5182
5183    let buffer = cx.new_model(|cx| {
5184        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
5185            .with_language(language, cx)
5186    });
5187    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5188    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5189    editor
5190        .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5191        .await;
5192
5193    _ = editor.update(cx, |editor, cx| {
5194        editor.change_selections(None, cx, |s| {
5195            s.select_ranges([
5196                Point::new(0, 1)..Point::new(0, 1),
5197                Point::new(1, 1)..Point::new(1, 1),
5198                Point::new(2, 1)..Point::new(2, 1),
5199            ])
5200        });
5201
5202        editor.handle_input("{", cx);
5203        editor.handle_input("{", cx);
5204        editor.handle_input("_", cx);
5205        assert_eq!(
5206            editor.text(cx),
5207            "
5208                a{{_}}
5209                b{{_}}
5210                c{{_}}
5211            "
5212            .unindent()
5213        );
5214        assert_eq!(
5215            editor.selections.ranges::<Point>(cx),
5216            [
5217                Point::new(0, 4)..Point::new(0, 4),
5218                Point::new(1, 4)..Point::new(1, 4),
5219                Point::new(2, 4)..Point::new(2, 4)
5220            ]
5221        );
5222
5223        editor.backspace(&Default::default(), cx);
5224        editor.backspace(&Default::default(), cx);
5225        assert_eq!(
5226            editor.text(cx),
5227            "
5228                a{}
5229                b{}
5230                c{}
5231            "
5232            .unindent()
5233        );
5234        assert_eq!(
5235            editor.selections.ranges::<Point>(cx),
5236            [
5237                Point::new(0, 2)..Point::new(0, 2),
5238                Point::new(1, 2)..Point::new(1, 2),
5239                Point::new(2, 2)..Point::new(2, 2)
5240            ]
5241        );
5242
5243        editor.delete_to_previous_word_start(&Default::default(), cx);
5244        assert_eq!(
5245            editor.text(cx),
5246            "
5247                a
5248                b
5249                c
5250            "
5251            .unindent()
5252        );
5253        assert_eq!(
5254            editor.selections.ranges::<Point>(cx),
5255            [
5256                Point::new(0, 1)..Point::new(0, 1),
5257                Point::new(1, 1)..Point::new(1, 1),
5258                Point::new(2, 1)..Point::new(2, 1)
5259            ]
5260        );
5261    });
5262}
5263
5264#[gpui::test]
5265async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
5266    init_test(cx, |settings| {
5267        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5268    });
5269
5270    let mut cx = EditorTestContext::new(cx).await;
5271
5272    let language = Arc::new(Language::new(
5273        LanguageConfig {
5274            brackets: BracketPairConfig {
5275                pairs: vec![
5276                    BracketPair {
5277                        start: "{".to_string(),
5278                        end: "}".to_string(),
5279                        close: true,
5280                        newline: true,
5281                    },
5282                    BracketPair {
5283                        start: "(".to_string(),
5284                        end: ")".to_string(),
5285                        close: true,
5286                        newline: true,
5287                    },
5288                    BracketPair {
5289                        start: "[".to_string(),
5290                        end: "]".to_string(),
5291                        close: false,
5292                        newline: true,
5293                    },
5294                ],
5295                ..Default::default()
5296            },
5297            autoclose_before: "})]".to_string(),
5298            ..Default::default()
5299        },
5300        Some(tree_sitter_rust::language()),
5301    ));
5302
5303    cx.language_registry().add(language.clone());
5304    cx.update_buffer(|buffer, cx| {
5305        buffer.set_language(Some(language), cx);
5306    });
5307
5308    cx.set_state(
5309        &"
5310            {(ˇ)}
5311            [[ˇ]]
5312            {(ˇ)}
5313        "
5314        .unindent(),
5315    );
5316
5317    cx.update_editor(|view, cx| {
5318        view.backspace(&Default::default(), cx);
5319        view.backspace(&Default::default(), cx);
5320    });
5321
5322    cx.assert_editor_state(
5323        &"
5324            ˇ
5325            ˇ]]
5326            ˇ
5327        "
5328        .unindent(),
5329    );
5330
5331    cx.update_editor(|view, cx| {
5332        view.handle_input("{", cx);
5333        view.handle_input("{", cx);
5334        view.move_right(&MoveRight, cx);
5335        view.move_right(&MoveRight, cx);
5336        view.move_left(&MoveLeft, cx);
5337        view.move_left(&MoveLeft, cx);
5338        view.backspace(&Default::default(), cx);
5339    });
5340
5341    cx.assert_editor_state(
5342        &"
5343            {ˇ}
5344            {ˇ}]]
5345            {ˇ}
5346        "
5347        .unindent(),
5348    );
5349
5350    cx.update_editor(|view, cx| {
5351        view.backspace(&Default::default(), cx);
5352    });
5353
5354    cx.assert_editor_state(
5355        &"
5356            ˇ
5357            ˇ]]
5358            ˇ
5359        "
5360        .unindent(),
5361    );
5362}
5363
5364#[gpui::test]
5365async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
5366    init_test(cx, |_| {});
5367
5368    let language = Arc::new(Language::new(
5369        LanguageConfig::default(),
5370        Some(tree_sitter_rust::language()),
5371    ));
5372
5373    let buffer = cx.new_model(|cx| {
5374        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "")
5375            .with_language(language, cx)
5376    });
5377    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5378    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5379    editor
5380        .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5381        .await;
5382
5383    _ = editor.update(cx, |editor, cx| {
5384        editor.set_auto_replace_emoji_shortcode(true);
5385
5386        editor.handle_input("Hello ", cx);
5387        editor.handle_input(":wave", cx);
5388        assert_eq!(editor.text(cx), "Hello :wave".unindent());
5389
5390        editor.handle_input(":", cx);
5391        assert_eq!(editor.text(cx), "Hello 👋".unindent());
5392
5393        editor.handle_input(" :smile", cx);
5394        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
5395
5396        editor.handle_input(":", cx);
5397        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
5398
5399        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
5400        editor.handle_input(":wave", cx);
5401        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
5402
5403        editor.handle_input(":", cx);
5404        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
5405
5406        editor.handle_input(":1", cx);
5407        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
5408
5409        editor.handle_input(":", cx);
5410        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
5411
5412        // Ensure shortcode does not get replaced when it is part of a word
5413        editor.handle_input(" Test:wave", cx);
5414        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
5415
5416        editor.handle_input(":", cx);
5417        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
5418
5419        editor.set_auto_replace_emoji_shortcode(false);
5420
5421        // Ensure shortcode does not get replaced when auto replace is off
5422        editor.handle_input(" :wave", cx);
5423        assert_eq!(
5424            editor.text(cx),
5425            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
5426        );
5427
5428        editor.handle_input(":", cx);
5429        assert_eq!(
5430            editor.text(cx),
5431            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
5432        );
5433    });
5434}
5435
5436#[gpui::test]
5437async fn test_snippets(cx: &mut gpui::TestAppContext) {
5438    init_test(cx, |_| {});
5439
5440    let (text, insertion_ranges) = marked_text_ranges(
5441        indoc! {"
5442            a.ˇ b
5443            a.ˇ b
5444            a.ˇ b
5445        "},
5446        false,
5447    );
5448
5449    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
5450    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5451
5452    _ = editor.update(cx, |editor, cx| {
5453        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
5454
5455        editor
5456            .insert_snippet(&insertion_ranges, snippet, cx)
5457            .unwrap();
5458
5459        fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
5460            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
5461            assert_eq!(editor.text(cx), expected_text);
5462            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
5463        }
5464
5465        assert(
5466            editor,
5467            cx,
5468            indoc! {"
5469                a.f(«one», two, «three») b
5470                a.f(«one», two, «three») b
5471                a.f(«one», two, «three») b
5472            "},
5473        );
5474
5475        // Can't move earlier than the first tab stop
5476        assert!(!editor.move_to_prev_snippet_tabstop(cx));
5477        assert(
5478            editor,
5479            cx,
5480            indoc! {"
5481                a.f(«one», two, «three») b
5482                a.f(«one», two, «three») b
5483                a.f(«one», two, «three») b
5484            "},
5485        );
5486
5487        assert!(editor.move_to_next_snippet_tabstop(cx));
5488        assert(
5489            editor,
5490            cx,
5491            indoc! {"
5492                a.f(one, «two», three) b
5493                a.f(one, «two», three) b
5494                a.f(one, «two», three) b
5495            "},
5496        );
5497
5498        editor.move_to_prev_snippet_tabstop(cx);
5499        assert(
5500            editor,
5501            cx,
5502            indoc! {"
5503                a.f(«one», two, «three») b
5504                a.f(«one», two, «three») b
5505                a.f(«one», two, «three») b
5506            "},
5507        );
5508
5509        assert!(editor.move_to_next_snippet_tabstop(cx));
5510        assert(
5511            editor,
5512            cx,
5513            indoc! {"
5514                a.f(one, «two», three) b
5515                a.f(one, «two», three) b
5516                a.f(one, «two», three) b
5517            "},
5518        );
5519        assert!(editor.move_to_next_snippet_tabstop(cx));
5520        assert(
5521            editor,
5522            cx,
5523            indoc! {"
5524                a.f(one, two, three)ˇ b
5525                a.f(one, two, three)ˇ b
5526                a.f(one, two, three)ˇ b
5527            "},
5528        );
5529
5530        // As soon as the last tab stop is reached, snippet state is gone
5531        editor.move_to_prev_snippet_tabstop(cx);
5532        assert(
5533            editor,
5534            cx,
5535            indoc! {"
5536                a.f(one, two, three)ˇ b
5537                a.f(one, two, three)ˇ b
5538                a.f(one, two, three)ˇ b
5539            "},
5540        );
5541    });
5542}
5543
5544#[gpui::test]
5545async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
5546    init_test(cx, |_| {});
5547
5548    let fs = FakeFs::new(cx.executor());
5549    fs.insert_file("/file.rs", Default::default()).await;
5550
5551    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5552
5553    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
5554    language_registry.add(rust_lang());
5555    let mut fake_servers = language_registry.register_fake_lsp_adapter(
5556        "Rust",
5557        FakeLspAdapter {
5558            capabilities: lsp::ServerCapabilities {
5559                document_formatting_provider: Some(lsp::OneOf::Left(true)),
5560                ..Default::default()
5561            },
5562            ..Default::default()
5563        },
5564    );
5565
5566    let buffer = project
5567        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5568        .await
5569        .unwrap();
5570
5571    cx.executor().start_waiting();
5572    let fake_server = fake_servers.next().await.unwrap();
5573
5574    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5575    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5576    _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5577    assert!(cx.read(|cx| editor.is_dirty(cx)));
5578
5579    let save = editor
5580        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5581        .unwrap();
5582    fake_server
5583        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5584            assert_eq!(
5585                params.text_document.uri,
5586                lsp::Url::from_file_path("/file.rs").unwrap()
5587            );
5588            assert_eq!(params.options.tab_size, 4);
5589            Ok(Some(vec![lsp::TextEdit::new(
5590                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5591                ", ".to_string(),
5592            )]))
5593        })
5594        .next()
5595        .await;
5596    cx.executor().start_waiting();
5597    let _x = save.await;
5598
5599    assert_eq!(
5600        editor.update(cx, |editor, cx| editor.text(cx)),
5601        "one, two\nthree\n"
5602    );
5603    assert!(!cx.read(|cx| editor.is_dirty(cx)));
5604
5605    _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5606    assert!(cx.read(|cx| editor.is_dirty(cx)));
5607
5608    // Ensure we can still save even if formatting hangs.
5609    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5610        assert_eq!(
5611            params.text_document.uri,
5612            lsp::Url::from_file_path("/file.rs").unwrap()
5613        );
5614        futures::future::pending::<()>().await;
5615        unreachable!()
5616    });
5617    let save = editor
5618        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5619        .unwrap();
5620    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5621    cx.executor().start_waiting();
5622    save.await;
5623    assert_eq!(
5624        editor.update(cx, |editor, cx| editor.text(cx)),
5625        "one\ntwo\nthree\n"
5626    );
5627    assert!(!cx.read(|cx| editor.is_dirty(cx)));
5628
5629    // Set rust language override and assert overridden tabsize is sent to language server
5630    update_test_language_settings(cx, |settings| {
5631        settings.languages.insert(
5632            "Rust".into(),
5633            LanguageSettingsContent {
5634                tab_size: NonZeroU32::new(8),
5635                ..Default::default()
5636            },
5637        );
5638    });
5639
5640    let save = editor
5641        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5642        .unwrap();
5643    fake_server
5644        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5645            assert_eq!(
5646                params.text_document.uri,
5647                lsp::Url::from_file_path("/file.rs").unwrap()
5648            );
5649            assert_eq!(params.options.tab_size, 8);
5650            Ok(Some(vec![]))
5651        })
5652        .next()
5653        .await;
5654    cx.executor().start_waiting();
5655    save.await;
5656}
5657
5658#[gpui::test]
5659async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
5660    init_test(cx, |_| {});
5661
5662    let fs = FakeFs::new(cx.executor());
5663    fs.insert_file("/file.rs", Default::default()).await;
5664
5665    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5666
5667    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
5668    language_registry.add(rust_lang());
5669    let mut fake_servers = language_registry.register_fake_lsp_adapter(
5670        "Rust",
5671        FakeLspAdapter {
5672            capabilities: lsp::ServerCapabilities {
5673                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
5674                ..Default::default()
5675            },
5676            ..Default::default()
5677        },
5678    );
5679
5680    let buffer = project
5681        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5682        .await
5683        .unwrap();
5684
5685    cx.executor().start_waiting();
5686    let fake_server = fake_servers.next().await.unwrap();
5687
5688    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5689    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5690    _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5691    assert!(cx.read(|cx| editor.is_dirty(cx)));
5692
5693    let save = editor
5694        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5695        .unwrap();
5696    fake_server
5697        .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
5698            assert_eq!(
5699                params.text_document.uri,
5700                lsp::Url::from_file_path("/file.rs").unwrap()
5701            );
5702            assert_eq!(params.options.tab_size, 4);
5703            Ok(Some(vec![lsp::TextEdit::new(
5704                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5705                ", ".to_string(),
5706            )]))
5707        })
5708        .next()
5709        .await;
5710    cx.executor().start_waiting();
5711    save.await;
5712    assert_eq!(
5713        editor.update(cx, |editor, cx| editor.text(cx)),
5714        "one, two\nthree\n"
5715    );
5716    assert!(!cx.read(|cx| editor.is_dirty(cx)));
5717
5718    _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5719    assert!(cx.read(|cx| editor.is_dirty(cx)));
5720
5721    // Ensure we can still save even if formatting hangs.
5722    fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
5723        move |params, _| async move {
5724            assert_eq!(
5725                params.text_document.uri,
5726                lsp::Url::from_file_path("/file.rs").unwrap()
5727            );
5728            futures::future::pending::<()>().await;
5729            unreachable!()
5730        },
5731    );
5732    let save = editor
5733        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5734        .unwrap();
5735    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5736    cx.executor().start_waiting();
5737    save.await;
5738    assert_eq!(
5739        editor.update(cx, |editor, cx| editor.text(cx)),
5740        "one\ntwo\nthree\n"
5741    );
5742    assert!(!cx.read(|cx| editor.is_dirty(cx)));
5743
5744    // Set rust language override and assert overridden tabsize is sent to language server
5745    update_test_language_settings(cx, |settings| {
5746        settings.languages.insert(
5747            "Rust".into(),
5748            LanguageSettingsContent {
5749                tab_size: NonZeroU32::new(8),
5750                ..Default::default()
5751            },
5752        );
5753    });
5754
5755    let save = editor
5756        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5757        .unwrap();
5758    fake_server
5759        .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
5760            assert_eq!(
5761                params.text_document.uri,
5762                lsp::Url::from_file_path("/file.rs").unwrap()
5763            );
5764            assert_eq!(params.options.tab_size, 8);
5765            Ok(Some(vec![]))
5766        })
5767        .next()
5768        .await;
5769    cx.executor().start_waiting();
5770    save.await;
5771}
5772
5773#[gpui::test]
5774async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
5775    init_test(cx, |settings| {
5776        settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer)
5777    });
5778
5779    let fs = FakeFs::new(cx.executor());
5780    fs.insert_file("/file.rs", Default::default()).await;
5781
5782    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5783
5784    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
5785    language_registry.add(Arc::new(Language::new(
5786        LanguageConfig {
5787            name: "Rust".into(),
5788            matcher: LanguageMatcher {
5789                path_suffixes: vec!["rs".to_string()],
5790                ..Default::default()
5791            },
5792            // Enable Prettier formatting for the same buffer, and ensure
5793            // LSP is called instead of Prettier.
5794            prettier_parser_name: Some("test_parser".to_string()),
5795            ..Default::default()
5796        },
5797        Some(tree_sitter_rust::language()),
5798    )));
5799    let mut fake_servers = language_registry.register_fake_lsp_adapter(
5800        "Rust",
5801        FakeLspAdapter {
5802            capabilities: lsp::ServerCapabilities {
5803                document_formatting_provider: Some(lsp::OneOf::Left(true)),
5804                ..Default::default()
5805            },
5806            ..Default::default()
5807        },
5808    );
5809
5810    let buffer = project
5811        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5812        .await
5813        .unwrap();
5814
5815    cx.executor().start_waiting();
5816    let fake_server = fake_servers.next().await.unwrap();
5817
5818    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5819    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5820    _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5821
5822    let format = editor
5823        .update(cx, |editor, cx| {
5824            editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
5825        })
5826        .unwrap();
5827    fake_server
5828        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5829            assert_eq!(
5830                params.text_document.uri,
5831                lsp::Url::from_file_path("/file.rs").unwrap()
5832            );
5833            assert_eq!(params.options.tab_size, 4);
5834            Ok(Some(vec![lsp::TextEdit::new(
5835                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5836                ", ".to_string(),
5837            )]))
5838        })
5839        .next()
5840        .await;
5841    cx.executor().start_waiting();
5842    format.await;
5843    assert_eq!(
5844        editor.update(cx, |editor, cx| editor.text(cx)),
5845        "one, two\nthree\n"
5846    );
5847
5848    _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5849    // Ensure we don't lock if formatting hangs.
5850    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5851        assert_eq!(
5852            params.text_document.uri,
5853            lsp::Url::from_file_path("/file.rs").unwrap()
5854        );
5855        futures::future::pending::<()>().await;
5856        unreachable!()
5857    });
5858    let format = editor
5859        .update(cx, |editor, cx| {
5860            editor.perform_format(project, FormatTrigger::Manual, cx)
5861        })
5862        .unwrap();
5863    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5864    cx.executor().start_waiting();
5865    format.await;
5866    assert_eq!(
5867        editor.update(cx, |editor, cx| editor.text(cx)),
5868        "one\ntwo\nthree\n"
5869    );
5870}
5871
5872#[gpui::test]
5873async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
5874    init_test(cx, |_| {});
5875
5876    let mut cx = EditorLspTestContext::new_rust(
5877        lsp::ServerCapabilities {
5878            document_formatting_provider: Some(lsp::OneOf::Left(true)),
5879            ..Default::default()
5880        },
5881        cx,
5882    )
5883    .await;
5884
5885    cx.set_state(indoc! {"
5886        one.twoˇ
5887    "});
5888
5889    // The format request takes a long time. When it completes, it inserts
5890    // a newline and an indent before the `.`
5891    cx.lsp
5892        .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
5893            let executor = cx.background_executor().clone();
5894            async move {
5895                executor.timer(Duration::from_millis(100)).await;
5896                Ok(Some(vec![lsp::TextEdit {
5897                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
5898                    new_text: "\n    ".into(),
5899                }]))
5900            }
5901        });
5902
5903    // Submit a format request.
5904    let format_1 = cx
5905        .update_editor(|editor, cx| editor.format(&Format, cx))
5906        .unwrap();
5907    cx.executor().run_until_parked();
5908
5909    // Submit a second format request.
5910    let format_2 = cx
5911        .update_editor(|editor, cx| editor.format(&Format, cx))
5912        .unwrap();
5913    cx.executor().run_until_parked();
5914
5915    // Wait for both format requests to complete
5916    cx.executor().advance_clock(Duration::from_millis(200));
5917    cx.executor().start_waiting();
5918    format_1.await.unwrap();
5919    cx.executor().start_waiting();
5920    format_2.await.unwrap();
5921
5922    // The formatting edits only happens once.
5923    cx.assert_editor_state(indoc! {"
5924        one
5925            .twoˇ
5926    "});
5927}
5928
5929#[gpui::test]
5930async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
5931    init_test(cx, |settings| {
5932        settings.defaults.formatter = Some(language_settings::Formatter::Auto)
5933    });
5934
5935    let mut cx = EditorLspTestContext::new_rust(
5936        lsp::ServerCapabilities {
5937            document_formatting_provider: Some(lsp::OneOf::Left(true)),
5938            ..Default::default()
5939        },
5940        cx,
5941    )
5942    .await;
5943
5944    // Set up a buffer white some trailing whitespace and no trailing newline.
5945    cx.set_state(
5946        &[
5947            "one ",   //
5948            "twoˇ",   //
5949            "three ", //
5950            "four",   //
5951        ]
5952        .join("\n"),
5953    );
5954
5955    // Submit a format request.
5956    let format = cx
5957        .update_editor(|editor, cx| editor.format(&Format, cx))
5958        .unwrap();
5959
5960    // Record which buffer changes have been sent to the language server
5961    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
5962    cx.lsp
5963        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
5964            let buffer_changes = buffer_changes.clone();
5965            move |params, _| {
5966                buffer_changes.lock().extend(
5967                    params
5968                        .content_changes
5969                        .into_iter()
5970                        .map(|e| (e.range.unwrap(), e.text)),
5971                );
5972            }
5973        });
5974
5975    // Handle formatting requests to the language server.
5976    cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
5977        let buffer_changes = buffer_changes.clone();
5978        move |_, _| {
5979            // When formatting is requested, trailing whitespace has already been stripped,
5980            // and the trailing newline has already been added.
5981            assert_eq!(
5982                &buffer_changes.lock()[1..],
5983                &[
5984                    (
5985                        lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
5986                        "".into()
5987                    ),
5988                    (
5989                        lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
5990                        "".into()
5991                    ),
5992                    (
5993                        lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
5994                        "\n".into()
5995                    ),
5996                ]
5997            );
5998
5999            // Insert blank lines between each line of the buffer.
6000            async move {
6001                Ok(Some(vec![
6002                    lsp::TextEdit {
6003                        range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
6004                        new_text: "\n".into(),
6005                    },
6006                    lsp::TextEdit {
6007                        range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
6008                        new_text: "\n".into(),
6009                    },
6010                ]))
6011            }
6012        }
6013    });
6014
6015    // After formatting the buffer, the trailing whitespace is stripped,
6016    // a newline is appended, and the edits provided by the language server
6017    // have been applied.
6018    format.await.unwrap();
6019    cx.assert_editor_state(
6020        &[
6021            "one",   //
6022            "",      //
6023            "twoˇ",  //
6024            "",      //
6025            "three", //
6026            "four",  //
6027            "",      //
6028        ]
6029        .join("\n"),
6030    );
6031
6032    // Undoing the formatting undoes the trailing whitespace removal, the
6033    // trailing newline, and the LSP edits.
6034    cx.update_buffer(|buffer, cx| buffer.undo(cx));
6035    cx.assert_editor_state(
6036        &[
6037            "one ",   //
6038            "twoˇ",   //
6039            "three ", //
6040            "four",   //
6041        ]
6042        .join("\n"),
6043    );
6044}
6045
6046#[gpui::test]
6047async fn test_completion(cx: &mut gpui::TestAppContext) {
6048    init_test(cx, |_| {});
6049
6050    let mut cx = EditorLspTestContext::new_rust(
6051        lsp::ServerCapabilities {
6052            completion_provider: Some(lsp::CompletionOptions {
6053                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
6054                resolve_provider: Some(true),
6055                ..Default::default()
6056            }),
6057            ..Default::default()
6058        },
6059        cx,
6060    )
6061    .await;
6062
6063    cx.set_state(indoc! {"
6064        oneˇ
6065        two
6066        three
6067    "});
6068    cx.simulate_keystroke(".");
6069    handle_completion_request(
6070        &mut cx,
6071        indoc! {"
6072            one.|<>
6073            two
6074            three
6075        "},
6076        vec!["first_completion", "second_completion"],
6077    )
6078    .await;
6079    cx.condition(|editor, _| editor.context_menu_visible())
6080        .await;
6081    let apply_additional_edits = cx.update_editor(|editor, cx| {
6082        editor.context_menu_next(&Default::default(), cx);
6083        editor
6084            .confirm_completion(&ConfirmCompletion::default(), cx)
6085            .unwrap()
6086    });
6087    cx.assert_editor_state(indoc! {"
6088        one.second_completionˇ
6089        two
6090        three
6091    "});
6092
6093    handle_resolve_completion_request(
6094        &mut cx,
6095        Some(vec![
6096            (
6097                //This overlaps with the primary completion edit which is
6098                //misbehavior from the LSP spec, test that we filter it out
6099                indoc! {"
6100                    one.second_ˇcompletion
6101                    two
6102                    threeˇ
6103                "},
6104                "overlapping additional edit",
6105            ),
6106            (
6107                indoc! {"
6108                    one.second_completion
6109                    two
6110                    threeˇ
6111                "},
6112                "\nadditional edit",
6113            ),
6114        ]),
6115    )
6116    .await;
6117    apply_additional_edits.await.unwrap();
6118    cx.assert_editor_state(indoc! {"
6119        one.second_completionˇ
6120        two
6121        three
6122        additional edit
6123    "});
6124
6125    cx.set_state(indoc! {"
6126        one.second_completion
6127        twoˇ
6128        threeˇ
6129        additional edit
6130    "});
6131    cx.simulate_keystroke(" ");
6132    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6133    cx.simulate_keystroke("s");
6134    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6135
6136    cx.assert_editor_state(indoc! {"
6137        one.second_completion
6138        two sˇ
6139        three sˇ
6140        additional edit
6141    "});
6142    handle_completion_request(
6143        &mut cx,
6144        indoc! {"
6145            one.second_completion
6146            two s
6147            three <s|>
6148            additional edit
6149        "},
6150        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
6151    )
6152    .await;
6153    cx.condition(|editor, _| editor.context_menu_visible())
6154        .await;
6155
6156    cx.simulate_keystroke("i");
6157
6158    handle_completion_request(
6159        &mut cx,
6160        indoc! {"
6161            one.second_completion
6162            two si
6163            three <si|>
6164            additional edit
6165        "},
6166        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
6167    )
6168    .await;
6169    cx.condition(|editor, _| editor.context_menu_visible())
6170        .await;
6171
6172    let apply_additional_edits = cx.update_editor(|editor, cx| {
6173        editor
6174            .confirm_completion(&ConfirmCompletion::default(), cx)
6175            .unwrap()
6176    });
6177    cx.assert_editor_state(indoc! {"
6178        one.second_completion
6179        two sixth_completionˇ
6180        three sixth_completionˇ
6181        additional edit
6182    "});
6183
6184    handle_resolve_completion_request(&mut cx, None).await;
6185    apply_additional_edits.await.unwrap();
6186
6187    _ = cx.update(|cx| {
6188        cx.update_global::<SettingsStore, _>(|settings, cx| {
6189            settings.update_user_settings::<EditorSettings>(cx, |settings| {
6190                settings.show_completions_on_input = Some(false);
6191            });
6192        })
6193    });
6194    cx.set_state("editorˇ");
6195    cx.simulate_keystroke(".");
6196    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6197    cx.simulate_keystroke("c");
6198    cx.simulate_keystroke("l");
6199    cx.simulate_keystroke("o");
6200    cx.assert_editor_state("editor.cloˇ");
6201    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6202    cx.update_editor(|editor, cx| {
6203        editor.show_completions(&ShowCompletions, cx);
6204    });
6205    handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
6206    cx.condition(|editor, _| editor.context_menu_visible())
6207        .await;
6208    let apply_additional_edits = cx.update_editor(|editor, cx| {
6209        editor
6210            .confirm_completion(&ConfirmCompletion::default(), cx)
6211            .unwrap()
6212    });
6213    cx.assert_editor_state("editor.closeˇ");
6214    handle_resolve_completion_request(&mut cx, None).await;
6215    apply_additional_edits.await.unwrap();
6216}
6217
6218#[gpui::test]
6219async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
6220    init_test(cx, |_| {});
6221    let mut cx = EditorTestContext::new(cx).await;
6222    let language = Arc::new(Language::new(
6223        LanguageConfig {
6224            line_comments: vec!["// ".into()],
6225            ..Default::default()
6226        },
6227        Some(tree_sitter_rust::language()),
6228    ));
6229    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
6230
6231    // If multiple selections intersect a line, the line is only toggled once.
6232    cx.set_state(indoc! {"
6233        fn a() {
6234            «//b();
6235            ˇ»// «c();
6236            //ˇ»  d();
6237        }
6238    "});
6239
6240    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6241
6242    cx.assert_editor_state(indoc! {"
6243        fn a() {
6244            «b();
6245            c();
6246            ˇ» d();
6247        }
6248    "});
6249
6250    // The comment prefix is inserted at the same column for every line in a
6251    // selection.
6252    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6253
6254    cx.assert_editor_state(indoc! {"
6255        fn a() {
6256            // «b();
6257            // c();
6258            ˇ»//  d();
6259        }
6260    "});
6261
6262    // If a selection ends at the beginning of a line, that line is not toggled.
6263    cx.set_selections_state(indoc! {"
6264        fn a() {
6265            // b();
6266            «// c();
6267        ˇ»    //  d();
6268        }
6269    "});
6270
6271    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6272
6273    cx.assert_editor_state(indoc! {"
6274        fn a() {
6275            // b();
6276            «c();
6277        ˇ»    //  d();
6278        }
6279    "});
6280
6281    // If a selection span a single line and is empty, the line is toggled.
6282    cx.set_state(indoc! {"
6283        fn a() {
6284            a();
6285            b();
6286        ˇ
6287        }
6288    "});
6289
6290    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6291
6292    cx.assert_editor_state(indoc! {"
6293        fn a() {
6294            a();
6295            b();
6296        //•ˇ
6297        }
6298    "});
6299
6300    // If a selection span multiple lines, empty lines are not toggled.
6301    cx.set_state(indoc! {"
6302        fn a() {
6303            «a();
6304
6305            c();ˇ»
6306        }
6307    "});
6308
6309    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6310
6311    cx.assert_editor_state(indoc! {"
6312        fn a() {
6313            // «a();
6314
6315            // c();ˇ»
6316        }
6317    "});
6318}
6319
6320#[gpui::test]
6321async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
6322    init_test(cx, |_| {});
6323
6324    let language = Arc::new(Language::new(
6325        LanguageConfig {
6326            line_comments: vec!["// ".into()],
6327            ..Default::default()
6328        },
6329        Some(tree_sitter_rust::language()),
6330    ));
6331
6332    let mut cx = EditorTestContext::new(cx).await;
6333
6334    cx.language_registry().add(language.clone());
6335    cx.update_buffer(|buffer, cx| {
6336        buffer.set_language(Some(language), cx);
6337    });
6338
6339    let toggle_comments = &ToggleComments {
6340        advance_downwards: true,
6341    };
6342
6343    // Single cursor on one line -> advance
6344    // Cursor moves horizontally 3 characters as well on non-blank line
6345    cx.set_state(indoc!(
6346        "fn a() {
6347             ˇdog();
6348             cat();
6349        }"
6350    ));
6351    cx.update_editor(|editor, cx| {
6352        editor.toggle_comments(toggle_comments, cx);
6353    });
6354    cx.assert_editor_state(indoc!(
6355        "fn a() {
6356             // dog();
6357             catˇ();
6358        }"
6359    ));
6360
6361    // Single selection on one line -> don't advance
6362    cx.set_state(indoc!(
6363        "fn a() {
6364             «dog()ˇ»;
6365             cat();
6366        }"
6367    ));
6368    cx.update_editor(|editor, cx| {
6369        editor.toggle_comments(toggle_comments, cx);
6370    });
6371    cx.assert_editor_state(indoc!(
6372        "fn a() {
6373             // «dog()ˇ»;
6374             cat();
6375        }"
6376    ));
6377
6378    // Multiple cursors on one line -> advance
6379    cx.set_state(indoc!(
6380        "fn a() {
6381             ˇdˇog();
6382             cat();
6383        }"
6384    ));
6385    cx.update_editor(|editor, cx| {
6386        editor.toggle_comments(toggle_comments, cx);
6387    });
6388    cx.assert_editor_state(indoc!(
6389        "fn a() {
6390             // dog();
6391             catˇ(ˇ);
6392        }"
6393    ));
6394
6395    // Multiple cursors on one line, with selection -> don't advance
6396    cx.set_state(indoc!(
6397        "fn a() {
6398             ˇdˇog«()ˇ»;
6399             cat();
6400        }"
6401    ));
6402    cx.update_editor(|editor, cx| {
6403        editor.toggle_comments(toggle_comments, cx);
6404    });
6405    cx.assert_editor_state(indoc!(
6406        "fn a() {
6407             // ˇdˇog«()ˇ»;
6408             cat();
6409        }"
6410    ));
6411
6412    // Single cursor on one line -> advance
6413    // Cursor moves to column 0 on blank line
6414    cx.set_state(indoc!(
6415        "fn a() {
6416             ˇdog();
6417
6418             cat();
6419        }"
6420    ));
6421    cx.update_editor(|editor, cx| {
6422        editor.toggle_comments(toggle_comments, cx);
6423    });
6424    cx.assert_editor_state(indoc!(
6425        "fn a() {
6426             // dog();
6427        ˇ
6428             cat();
6429        }"
6430    ));
6431
6432    // Single cursor on one line -> advance
6433    // Cursor starts and ends at column 0
6434    cx.set_state(indoc!(
6435        "fn a() {
6436         ˇ    dog();
6437             cat();
6438        }"
6439    ));
6440    cx.update_editor(|editor, cx| {
6441        editor.toggle_comments(toggle_comments, cx);
6442    });
6443    cx.assert_editor_state(indoc!(
6444        "fn a() {
6445             // dog();
6446         ˇ    cat();
6447        }"
6448    ));
6449}
6450
6451#[gpui::test]
6452async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
6453    init_test(cx, |_| {});
6454
6455    let mut cx = EditorTestContext::new(cx).await;
6456
6457    let html_language = Arc::new(
6458        Language::new(
6459            LanguageConfig {
6460                name: "HTML".into(),
6461                block_comment: Some(("<!-- ".into(), " -->".into())),
6462                ..Default::default()
6463            },
6464            Some(tree_sitter_html::language()),
6465        )
6466        .with_injection_query(
6467            r#"
6468            (script_element
6469                (raw_text) @content
6470                (#set! "language" "javascript"))
6471            "#,
6472        )
6473        .unwrap(),
6474    );
6475
6476    let javascript_language = Arc::new(Language::new(
6477        LanguageConfig {
6478            name: "JavaScript".into(),
6479            line_comments: vec!["// ".into()],
6480            ..Default::default()
6481        },
6482        Some(tree_sitter_typescript::language_tsx()),
6483    ));
6484
6485    cx.language_registry().add(html_language.clone());
6486    cx.language_registry().add(javascript_language.clone());
6487    cx.update_buffer(|buffer, cx| {
6488        buffer.set_language(Some(html_language), cx);
6489    });
6490
6491    // Toggle comments for empty selections
6492    cx.set_state(
6493        &r#"
6494            <p>A</p>ˇ
6495            <p>B</p>ˇ
6496            <p>C</p>ˇ
6497        "#
6498        .unindent(),
6499    );
6500    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6501    cx.assert_editor_state(
6502        &r#"
6503            <!-- <p>A</p>ˇ -->
6504            <!-- <p>B</p>ˇ -->
6505            <!-- <p>C</p>ˇ -->
6506        "#
6507        .unindent(),
6508    );
6509    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6510    cx.assert_editor_state(
6511        &r#"
6512            <p>A</p>ˇ
6513            <p>B</p>ˇ
6514            <p>C</p>ˇ
6515        "#
6516        .unindent(),
6517    );
6518
6519    // Toggle comments for mixture of empty and non-empty selections, where
6520    // multiple selections occupy a given line.
6521    cx.set_state(
6522        &r#"
6523            <p>A«</p>
6524            <p>ˇ»B</p>ˇ
6525            <p>C«</p>
6526            <p>ˇ»D</p>ˇ
6527        "#
6528        .unindent(),
6529    );
6530
6531    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6532    cx.assert_editor_state(
6533        &r#"
6534            <!-- <p>A«</p>
6535            <p>ˇ»B</p>ˇ -->
6536            <!-- <p>C«</p>
6537            <p>ˇ»D</p>ˇ -->
6538        "#
6539        .unindent(),
6540    );
6541    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6542    cx.assert_editor_state(
6543        &r#"
6544            <p>A«</p>
6545            <p>ˇ»B</p>ˇ
6546            <p>C«</p>
6547            <p>ˇ»D</p>ˇ
6548        "#
6549        .unindent(),
6550    );
6551
6552    // Toggle comments when different languages are active for different
6553    // selections.
6554    cx.set_state(
6555        &r#"
6556            ˇ<script>
6557                ˇvar x = new Y();
6558            ˇ</script>
6559        "#
6560        .unindent(),
6561    );
6562    cx.executor().run_until_parked();
6563    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6564    cx.assert_editor_state(
6565        &r#"
6566            <!-- ˇ<script> -->
6567                // ˇvar x = new Y();
6568            <!-- ˇ</script> -->
6569        "#
6570        .unindent(),
6571    );
6572}
6573
6574#[gpui::test]
6575fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
6576    init_test(cx, |_| {});
6577
6578    let buffer = cx.new_model(|cx| {
6579        Buffer::new(
6580            0,
6581            BufferId::new(cx.entity_id().as_u64()).unwrap(),
6582            sample_text(3, 4, 'a'),
6583        )
6584    });
6585    let multibuffer = cx.new_model(|cx| {
6586        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
6587        multibuffer.push_excerpts(
6588            buffer.clone(),
6589            [
6590                ExcerptRange {
6591                    context: Point::new(0, 0)..Point::new(0, 4),
6592                    primary: None,
6593                },
6594                ExcerptRange {
6595                    context: Point::new(1, 0)..Point::new(1, 4),
6596                    primary: None,
6597                },
6598            ],
6599            cx,
6600        );
6601        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
6602        multibuffer
6603    });
6604
6605    let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
6606    _ = view.update(cx, |view, cx| {
6607        assert_eq!(view.text(cx), "aaaa\nbbbb");
6608        view.change_selections(None, cx, |s| {
6609            s.select_ranges([
6610                Point::new(0, 0)..Point::new(0, 0),
6611                Point::new(1, 0)..Point::new(1, 0),
6612            ])
6613        });
6614
6615        view.handle_input("X", cx);
6616        assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
6617        assert_eq!(
6618            view.selections.ranges(cx),
6619            [
6620                Point::new(0, 1)..Point::new(0, 1),
6621                Point::new(1, 1)..Point::new(1, 1),
6622            ]
6623        );
6624
6625        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
6626        view.change_selections(None, cx, |s| {
6627            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
6628        });
6629        view.backspace(&Default::default(), cx);
6630        assert_eq!(view.text(cx), "Xa\nbbb");
6631        assert_eq!(
6632            view.selections.ranges(cx),
6633            [Point::new(1, 0)..Point::new(1, 0)]
6634        );
6635
6636        view.change_selections(None, cx, |s| {
6637            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
6638        });
6639        view.backspace(&Default::default(), cx);
6640        assert_eq!(view.text(cx), "X\nbb");
6641        assert_eq!(
6642            view.selections.ranges(cx),
6643            [Point::new(0, 1)..Point::new(0, 1)]
6644        );
6645    });
6646}
6647
6648#[gpui::test]
6649fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
6650    init_test(cx, |_| {});
6651
6652    let markers = vec![('[', ']').into(), ('(', ')').into()];
6653    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
6654        indoc! {"
6655            [aaaa
6656            (bbbb]
6657            cccc)",
6658        },
6659        markers.clone(),
6660    );
6661    let excerpt_ranges = markers.into_iter().map(|marker| {
6662        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
6663        ExcerptRange {
6664            context,
6665            primary: None,
6666        }
6667    });
6668    let buffer = cx.new_model(|cx| {
6669        Buffer::new(
6670            0,
6671            BufferId::new(cx.entity_id().as_u64()).unwrap(),
6672            initial_text,
6673        )
6674    });
6675    let multibuffer = cx.new_model(|cx| {
6676        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
6677        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
6678        multibuffer
6679    });
6680
6681    let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
6682    _ = view.update(cx, |view, cx| {
6683        let (expected_text, selection_ranges) = marked_text_ranges(
6684            indoc! {"
6685                aaaa
6686                bˇbbb
6687                bˇbbˇb
6688                cccc"
6689            },
6690            true,
6691        );
6692        assert_eq!(view.text(cx), expected_text);
6693        view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
6694
6695        view.handle_input("X", cx);
6696
6697        let (expected_text, expected_selections) = marked_text_ranges(
6698            indoc! {"
6699                aaaa
6700                bXˇbbXb
6701                bXˇbbXˇb
6702                cccc"
6703            },
6704            false,
6705        );
6706        assert_eq!(view.text(cx), expected_text);
6707        assert_eq!(view.selections.ranges(cx), expected_selections);
6708
6709        view.newline(&Newline, cx);
6710        let (expected_text, expected_selections) = marked_text_ranges(
6711            indoc! {"
6712                aaaa
6713                bX
6714                ˇbbX
6715                b
6716                bX
6717                ˇbbX
6718                ˇb
6719                cccc"
6720            },
6721            false,
6722        );
6723        assert_eq!(view.text(cx), expected_text);
6724        assert_eq!(view.selections.ranges(cx), expected_selections);
6725    });
6726}
6727
6728#[gpui::test]
6729fn test_refresh_selections(cx: &mut TestAppContext) {
6730    init_test(cx, |_| {});
6731
6732    let buffer = cx.new_model(|cx| {
6733        Buffer::new(
6734            0,
6735            BufferId::new(cx.entity_id().as_u64()).unwrap(),
6736            sample_text(3, 4, 'a'),
6737        )
6738    });
6739    let mut excerpt1_id = None;
6740    let multibuffer = cx.new_model(|cx| {
6741        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
6742        excerpt1_id = multibuffer
6743            .push_excerpts(
6744                buffer.clone(),
6745                [
6746                    ExcerptRange {
6747                        context: Point::new(0, 0)..Point::new(1, 4),
6748                        primary: None,
6749                    },
6750                    ExcerptRange {
6751                        context: Point::new(1, 0)..Point::new(2, 4),
6752                        primary: None,
6753                    },
6754                ],
6755                cx,
6756            )
6757            .into_iter()
6758            .next();
6759        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6760        multibuffer
6761    });
6762
6763    let editor = cx.add_window(|cx| {
6764        let mut editor = build_editor(multibuffer.clone(), cx);
6765        let snapshot = editor.snapshot(cx);
6766        editor.change_selections(None, cx, |s| {
6767            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
6768        });
6769        editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
6770        assert_eq!(
6771            editor.selections.ranges(cx),
6772            [
6773                Point::new(1, 3)..Point::new(1, 3),
6774                Point::new(2, 1)..Point::new(2, 1),
6775            ]
6776        );
6777        editor
6778    });
6779
6780    // Refreshing selections is a no-op when excerpts haven't changed.
6781    _ = editor.update(cx, |editor, cx| {
6782        editor.change_selections(None, cx, |s| s.refresh());
6783        assert_eq!(
6784            editor.selections.ranges(cx),
6785            [
6786                Point::new(1, 3)..Point::new(1, 3),
6787                Point::new(2, 1)..Point::new(2, 1),
6788            ]
6789        );
6790    });
6791
6792    _ = multibuffer.update(cx, |multibuffer, cx| {
6793        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6794    });
6795    _ = editor.update(cx, |editor, cx| {
6796        // Removing an excerpt causes the first selection to become degenerate.
6797        assert_eq!(
6798            editor.selections.ranges(cx),
6799            [
6800                Point::new(0, 0)..Point::new(0, 0),
6801                Point::new(0, 1)..Point::new(0, 1)
6802            ]
6803        );
6804
6805        // Refreshing selections will relocate the first selection to the original buffer
6806        // location.
6807        editor.change_selections(None, cx, |s| s.refresh());
6808        assert_eq!(
6809            editor.selections.ranges(cx),
6810            [
6811                Point::new(0, 1)..Point::new(0, 1),
6812                Point::new(0, 3)..Point::new(0, 3)
6813            ]
6814        );
6815        assert!(editor.selections.pending_anchor().is_some());
6816    });
6817}
6818
6819#[gpui::test]
6820fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
6821    init_test(cx, |_| {});
6822
6823    let buffer = cx.new_model(|cx| {
6824        Buffer::new(
6825            0,
6826            BufferId::new(cx.entity_id().as_u64()).unwrap(),
6827            sample_text(3, 4, 'a'),
6828        )
6829    });
6830    let mut excerpt1_id = None;
6831    let multibuffer = cx.new_model(|cx| {
6832        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
6833        excerpt1_id = multibuffer
6834            .push_excerpts(
6835                buffer.clone(),
6836                [
6837                    ExcerptRange {
6838                        context: Point::new(0, 0)..Point::new(1, 4),
6839                        primary: None,
6840                    },
6841                    ExcerptRange {
6842                        context: Point::new(1, 0)..Point::new(2, 4),
6843                        primary: None,
6844                    },
6845                ],
6846                cx,
6847            )
6848            .into_iter()
6849            .next();
6850        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6851        multibuffer
6852    });
6853
6854    let editor = cx.add_window(|cx| {
6855        let mut editor = build_editor(multibuffer.clone(), cx);
6856        let snapshot = editor.snapshot(cx);
6857        editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
6858        assert_eq!(
6859            editor.selections.ranges(cx),
6860            [Point::new(1, 3)..Point::new(1, 3)]
6861        );
6862        editor
6863    });
6864
6865    _ = multibuffer.update(cx, |multibuffer, cx| {
6866        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6867    });
6868    _ = editor.update(cx, |editor, cx| {
6869        assert_eq!(
6870            editor.selections.ranges(cx),
6871            [Point::new(0, 0)..Point::new(0, 0)]
6872        );
6873
6874        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
6875        editor.change_selections(None, cx, |s| s.refresh());
6876        assert_eq!(
6877            editor.selections.ranges(cx),
6878            [Point::new(0, 3)..Point::new(0, 3)]
6879        );
6880        assert!(editor.selections.pending_anchor().is_some());
6881    });
6882}
6883
6884#[gpui::test]
6885async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
6886    init_test(cx, |_| {});
6887
6888    let language = Arc::new(
6889        Language::new(
6890            LanguageConfig {
6891                brackets: BracketPairConfig {
6892                    pairs: vec![
6893                        BracketPair {
6894                            start: "{".to_string(),
6895                            end: "}".to_string(),
6896                            close: true,
6897                            newline: true,
6898                        },
6899                        BracketPair {
6900                            start: "/* ".to_string(),
6901                            end: " */".to_string(),
6902                            close: true,
6903                            newline: true,
6904                        },
6905                    ],
6906                    ..Default::default()
6907                },
6908                ..Default::default()
6909            },
6910            Some(tree_sitter_rust::language()),
6911        )
6912        .with_indents_query("")
6913        .unwrap(),
6914    );
6915
6916    let text = concat!(
6917        "{   }\n",     //
6918        "  x\n",       //
6919        "  /*   */\n", //
6920        "x\n",         //
6921        "{{} }\n",     //
6922    );
6923
6924    let buffer = cx.new_model(|cx| {
6925        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
6926            .with_language(language, cx)
6927    });
6928    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6929    let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6930    view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6931        .await;
6932
6933    _ = view.update(cx, |view, cx| {
6934        view.change_selections(None, cx, |s| {
6935            s.select_display_ranges([
6936                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
6937                DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
6938                DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
6939            ])
6940        });
6941        view.newline(&Newline, cx);
6942
6943        assert_eq!(
6944            view.buffer().read(cx).read(cx).text(),
6945            concat!(
6946                "{ \n",    // Suppress rustfmt
6947                "\n",      //
6948                "}\n",     //
6949                "  x\n",   //
6950                "  /* \n", //
6951                "  \n",    //
6952                "  */\n",  //
6953                "x\n",     //
6954                "{{} \n",  //
6955                "}\n",     //
6956            )
6957        );
6958    });
6959}
6960
6961#[gpui::test]
6962fn test_highlighted_ranges(cx: &mut TestAppContext) {
6963    init_test(cx, |_| {});
6964
6965    let editor = cx.add_window(|cx| {
6966        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
6967        build_editor(buffer.clone(), cx)
6968    });
6969
6970    _ = editor.update(cx, |editor, cx| {
6971        struct Type1;
6972        struct Type2;
6973
6974        let buffer = editor.buffer.read(cx).snapshot(cx);
6975
6976        let anchor_range =
6977            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
6978
6979        editor.highlight_background::<Type1>(
6980            vec![
6981                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
6982                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
6983                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
6984                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
6985            ],
6986            |_| Hsla::red(),
6987            cx,
6988        );
6989        editor.highlight_background::<Type2>(
6990            vec![
6991                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
6992                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
6993                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
6994                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
6995            ],
6996            |_| Hsla::green(),
6997            cx,
6998        );
6999
7000        let snapshot = editor.snapshot(cx);
7001        let mut highlighted_ranges = editor.background_highlights_in_range(
7002            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
7003            &snapshot,
7004            cx.theme().colors(),
7005        );
7006        // Enforce a consistent ordering based on color without relying on the ordering of the
7007        // highlight's `TypeId` which is non-executor.
7008        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
7009        assert_eq!(
7010            highlighted_ranges,
7011            &[
7012                (
7013                    DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
7014                    Hsla::red(),
7015                ),
7016                (
7017                    DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
7018                    Hsla::red(),
7019                ),
7020                (
7021                    DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
7022                    Hsla::green(),
7023                ),
7024                (
7025                    DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
7026                    Hsla::green(),
7027                ),
7028            ]
7029        );
7030        assert_eq!(
7031            editor.background_highlights_in_range(
7032                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
7033                &snapshot,
7034                cx.theme().colors(),
7035            ),
7036            &[(
7037                DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
7038                Hsla::red(),
7039            )]
7040        );
7041    });
7042}
7043
7044#[gpui::test]
7045async fn test_following(cx: &mut gpui::TestAppContext) {
7046    init_test(cx, |_| {});
7047
7048    let fs = FakeFs::new(cx.executor());
7049    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7050
7051    let buffer = project.update(cx, |project, cx| {
7052        let buffer = project
7053            .create_buffer(&sample_text(16, 8, 'a'), None, cx)
7054            .unwrap();
7055        cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
7056    });
7057    let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
7058    let follower = cx.update(|cx| {
7059        cx.open_window(
7060            WindowOptions {
7061                bounds: Some(Bounds::from_corners(
7062                    gpui::Point::new(0_f64.into(), 0_f64.into()),
7063                    gpui::Point::new(10_f64.into(), 80_f64.into()),
7064                )),
7065                ..Default::default()
7066            },
7067            |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
7068        )
7069    });
7070
7071    let is_still_following = Rc::new(RefCell::new(true));
7072    let follower_edit_event_count = Rc::new(RefCell::new(0));
7073    let pending_update = Rc::new(RefCell::new(None));
7074    _ = follower.update(cx, {
7075        let update = pending_update.clone();
7076        let is_still_following = is_still_following.clone();
7077        let follower_edit_event_count = follower_edit_event_count.clone();
7078        |_, cx| {
7079            cx.subscribe(
7080                &leader.root_view(cx).unwrap(),
7081                move |_, leader, event, cx| {
7082                    leader
7083                        .read(cx)
7084                        .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
7085                },
7086            )
7087            .detach();
7088
7089            cx.subscribe(
7090                &follower.root_view(cx).unwrap(),
7091                move |_, _, event: &EditorEvent, _cx| {
7092                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
7093                        *is_still_following.borrow_mut() = false;
7094                    }
7095
7096                    if let EditorEvent::BufferEdited = event {
7097                        *follower_edit_event_count.borrow_mut() += 1;
7098                    }
7099                },
7100            )
7101            .detach();
7102        }
7103    });
7104
7105    // Update the selections only
7106    _ = leader.update(cx, |leader, cx| {
7107        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
7108    });
7109    follower
7110        .update(cx, |follower, cx| {
7111            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7112        })
7113        .unwrap()
7114        .await
7115        .unwrap();
7116    _ = follower.update(cx, |follower, cx| {
7117        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
7118    });
7119    assert_eq!(*is_still_following.borrow(), true);
7120    assert_eq!(*follower_edit_event_count.borrow(), 0);
7121
7122    // Update the scroll position only
7123    _ = leader.update(cx, |leader, cx| {
7124        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
7125    });
7126    follower
7127        .update(cx, |follower, cx| {
7128            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7129        })
7130        .unwrap()
7131        .await
7132        .unwrap();
7133    assert_eq!(
7134        follower
7135            .update(cx, |follower, cx| follower.scroll_position(cx))
7136            .unwrap(),
7137        gpui::Point::new(1.5, 3.5)
7138    );
7139    assert_eq!(*is_still_following.borrow(), true);
7140    assert_eq!(*follower_edit_event_count.borrow(), 0);
7141
7142    // Update the selections and scroll position. The follower's scroll position is updated
7143    // via autoscroll, not via the leader's exact scroll position.
7144    _ = leader.update(cx, |leader, cx| {
7145        leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
7146        leader.request_autoscroll(Autoscroll::newest(), cx);
7147        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
7148    });
7149    follower
7150        .update(cx, |follower, cx| {
7151            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7152        })
7153        .unwrap()
7154        .await
7155        .unwrap();
7156    _ = follower.update(cx, |follower, cx| {
7157        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
7158        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
7159    });
7160    assert_eq!(*is_still_following.borrow(), true);
7161
7162    // Creating a pending selection that precedes another selection
7163    _ = leader.update(cx, |leader, cx| {
7164        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
7165        leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
7166    });
7167    follower
7168        .update(cx, |follower, cx| {
7169            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7170        })
7171        .unwrap()
7172        .await
7173        .unwrap();
7174    _ = follower.update(cx, |follower, cx| {
7175        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
7176    });
7177    assert_eq!(*is_still_following.borrow(), true);
7178
7179    // Extend the pending selection so that it surrounds another selection
7180    _ = leader.update(cx, |leader, cx| {
7181        leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
7182    });
7183    follower
7184        .update(cx, |follower, cx| {
7185            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7186        })
7187        .unwrap()
7188        .await
7189        .unwrap();
7190    _ = follower.update(cx, |follower, cx| {
7191        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
7192    });
7193
7194    // Scrolling locally breaks the follow
7195    _ = follower.update(cx, |follower, cx| {
7196        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
7197        follower.set_scroll_anchor(
7198            ScrollAnchor {
7199                anchor: top_anchor,
7200                offset: gpui::Point::new(0.0, 0.5),
7201            },
7202            cx,
7203        );
7204    });
7205    assert_eq!(*is_still_following.borrow(), false);
7206}
7207
7208#[gpui::test]
7209async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
7210    init_test(cx, |_| {});
7211
7212    let fs = FakeFs::new(cx.executor());
7213    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7214    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7215    let pane = workspace
7216        .update(cx, |workspace, _| workspace.active_pane().clone())
7217        .unwrap();
7218
7219    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7220
7221    let leader = pane.update(cx, |_, cx| {
7222        let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
7223        cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
7224    });
7225
7226    // Start following the editor when it has no excerpts.
7227    let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
7228    let follower_1 = cx
7229        .update_window(*workspace.deref(), |_, cx| {
7230            Editor::from_state_proto(
7231                pane.clone(),
7232                workspace.root_view(cx).unwrap(),
7233                ViewId {
7234                    creator: Default::default(),
7235                    id: 0,
7236                },
7237                &mut state_message,
7238                cx,
7239            )
7240        })
7241        .unwrap()
7242        .unwrap()
7243        .await
7244        .unwrap();
7245
7246    let update_message = Rc::new(RefCell::new(None));
7247    follower_1.update(cx, {
7248        let update = update_message.clone();
7249        |_, cx| {
7250            cx.subscribe(&leader, move |_, leader, event, cx| {
7251                leader
7252                    .read(cx)
7253                    .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
7254            })
7255            .detach();
7256        }
7257    });
7258
7259    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
7260        (
7261            project
7262                .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
7263                .unwrap(),
7264            project
7265                .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
7266                .unwrap(),
7267        )
7268    });
7269
7270    // Insert some excerpts.
7271    _ = leader.update(cx, |leader, cx| {
7272        leader.buffer.update(cx, |multibuffer, cx| {
7273            let excerpt_ids = multibuffer.push_excerpts(
7274                buffer_1.clone(),
7275                [
7276                    ExcerptRange {
7277                        context: 1..6,
7278                        primary: None,
7279                    },
7280                    ExcerptRange {
7281                        context: 12..15,
7282                        primary: None,
7283                    },
7284                    ExcerptRange {
7285                        context: 0..3,
7286                        primary: None,
7287                    },
7288                ],
7289                cx,
7290            );
7291            multibuffer.insert_excerpts_after(
7292                excerpt_ids[0],
7293                buffer_2.clone(),
7294                [
7295                    ExcerptRange {
7296                        context: 8..12,
7297                        primary: None,
7298                    },
7299                    ExcerptRange {
7300                        context: 0..6,
7301                        primary: None,
7302                    },
7303                ],
7304                cx,
7305            );
7306        });
7307    });
7308
7309    // Apply the update of adding the excerpts.
7310    follower_1
7311        .update(cx, |follower, cx| {
7312            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
7313        })
7314        .await
7315        .unwrap();
7316    assert_eq!(
7317        follower_1.update(cx, |editor, cx| editor.text(cx)),
7318        leader.update(cx, |editor, cx| editor.text(cx))
7319    );
7320    update_message.borrow_mut().take();
7321
7322    // Start following separately after it already has excerpts.
7323    let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
7324    let follower_2 = cx
7325        .update_window(*workspace.deref(), |_, cx| {
7326            Editor::from_state_proto(
7327                pane.clone(),
7328                workspace.root_view(cx).unwrap().clone(),
7329                ViewId {
7330                    creator: Default::default(),
7331                    id: 0,
7332                },
7333                &mut state_message,
7334                cx,
7335            )
7336        })
7337        .unwrap()
7338        .unwrap()
7339        .await
7340        .unwrap();
7341    assert_eq!(
7342        follower_2.update(cx, |editor, cx| editor.text(cx)),
7343        leader.update(cx, |editor, cx| editor.text(cx))
7344    );
7345
7346    // Remove some excerpts.
7347    _ = leader.update(cx, |leader, cx| {
7348        leader.buffer.update(cx, |multibuffer, cx| {
7349            let excerpt_ids = multibuffer.excerpt_ids();
7350            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
7351            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
7352        });
7353    });
7354
7355    // Apply the update of removing the excerpts.
7356    follower_1
7357        .update(cx, |follower, cx| {
7358            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
7359        })
7360        .await
7361        .unwrap();
7362    follower_2
7363        .update(cx, |follower, cx| {
7364            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
7365        })
7366        .await
7367        .unwrap();
7368    update_message.borrow_mut().take();
7369    assert_eq!(
7370        follower_1.update(cx, |editor, cx| editor.text(cx)),
7371        leader.update(cx, |editor, cx| editor.text(cx))
7372    );
7373}
7374
7375#[gpui::test]
7376async fn go_to_prev_overlapping_diagnostic(
7377    executor: BackgroundExecutor,
7378    cx: &mut gpui::TestAppContext,
7379) {
7380    init_test(cx, |_| {});
7381
7382    let mut cx = EditorTestContext::new(cx).await;
7383    let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
7384
7385    cx.set_state(indoc! {"
7386        ˇfn func(abc def: i32) -> u32 {
7387        }
7388    "});
7389
7390    _ = cx.update(|cx| {
7391        _ = project.update(cx, |project, cx| {
7392            project
7393                .update_diagnostics(
7394                    LanguageServerId(0),
7395                    lsp::PublishDiagnosticsParams {
7396                        uri: lsp::Url::from_file_path("/root/file").unwrap(),
7397                        version: None,
7398                        diagnostics: vec![
7399                            lsp::Diagnostic {
7400                                range: lsp::Range::new(
7401                                    lsp::Position::new(0, 11),
7402                                    lsp::Position::new(0, 12),
7403                                ),
7404                                severity: Some(lsp::DiagnosticSeverity::ERROR),
7405                                ..Default::default()
7406                            },
7407                            lsp::Diagnostic {
7408                                range: lsp::Range::new(
7409                                    lsp::Position::new(0, 12),
7410                                    lsp::Position::new(0, 15),
7411                                ),
7412                                severity: Some(lsp::DiagnosticSeverity::ERROR),
7413                                ..Default::default()
7414                            },
7415                            lsp::Diagnostic {
7416                                range: lsp::Range::new(
7417                                    lsp::Position::new(0, 25),
7418                                    lsp::Position::new(0, 28),
7419                                ),
7420                                severity: Some(lsp::DiagnosticSeverity::ERROR),
7421                                ..Default::default()
7422                            },
7423                        ],
7424                    },
7425                    &[],
7426                    cx,
7427                )
7428                .unwrap()
7429        });
7430    });
7431
7432    executor.run_until_parked();
7433
7434    cx.update_editor(|editor, cx| {
7435        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
7436    });
7437
7438    cx.assert_editor_state(indoc! {"
7439        fn func(abc def: i32) -> ˇu32 {
7440        }
7441    "});
7442
7443    cx.update_editor(|editor, cx| {
7444        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
7445    });
7446
7447    cx.assert_editor_state(indoc! {"
7448        fn func(abc ˇdef: i32) -> u32 {
7449        }
7450    "});
7451
7452    cx.update_editor(|editor, cx| {
7453        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
7454    });
7455
7456    cx.assert_editor_state(indoc! {"
7457        fn func(abcˇ def: i32) -> u32 {
7458        }
7459    "});
7460
7461    cx.update_editor(|editor, cx| {
7462        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
7463    });
7464
7465    cx.assert_editor_state(indoc! {"
7466        fn func(abc def: i32) -> ˇu32 {
7467        }
7468    "});
7469}
7470
7471#[gpui::test]
7472async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7473    init_test(cx, |_| {});
7474
7475    let mut cx = EditorTestContext::new(cx).await;
7476
7477    let diff_base = r#"
7478        use some::mod;
7479
7480        const A: u32 = 42;
7481
7482        fn main() {
7483            println!("hello");
7484
7485            println!("world");
7486        }
7487        "#
7488    .unindent();
7489
7490    // Edits are modified, removed, modified, added
7491    cx.set_state(
7492        &r#"
7493        use some::modified;
7494
7495        ˇ
7496        fn main() {
7497            println!("hello there");
7498
7499            println!("around the");
7500            println!("world");
7501        }
7502        "#
7503        .unindent(),
7504    );
7505
7506    cx.set_diff_base(Some(&diff_base));
7507    executor.run_until_parked();
7508
7509    cx.update_editor(|editor, cx| {
7510        //Wrap around the bottom of the buffer
7511        for _ in 0..3 {
7512            editor.go_to_hunk(&GoToHunk, cx);
7513        }
7514    });
7515
7516    cx.assert_editor_state(
7517        &r#"
7518        ˇuse some::modified;
7519
7520
7521        fn main() {
7522            println!("hello there");
7523
7524            println!("around the");
7525            println!("world");
7526        }
7527        "#
7528        .unindent(),
7529    );
7530
7531    cx.update_editor(|editor, cx| {
7532        //Wrap around the top of the buffer
7533        for _ in 0..2 {
7534            editor.go_to_prev_hunk(&GoToPrevHunk, cx);
7535        }
7536    });
7537
7538    cx.assert_editor_state(
7539        &r#"
7540        use some::modified;
7541
7542
7543        fn main() {
7544        ˇ    println!("hello there");
7545
7546            println!("around the");
7547            println!("world");
7548        }
7549        "#
7550        .unindent(),
7551    );
7552
7553    cx.update_editor(|editor, cx| {
7554        editor.go_to_prev_hunk(&GoToPrevHunk, cx);
7555    });
7556
7557    cx.assert_editor_state(
7558        &r#"
7559        use some::modified;
7560
7561        ˇ
7562        fn main() {
7563            println!("hello there");
7564
7565            println!("around the");
7566            println!("world");
7567        }
7568        "#
7569        .unindent(),
7570    );
7571
7572    cx.update_editor(|editor, cx| {
7573        for _ in 0..3 {
7574            editor.go_to_prev_hunk(&GoToPrevHunk, cx);
7575        }
7576    });
7577
7578    cx.assert_editor_state(
7579        &r#"
7580        use some::modified;
7581
7582
7583        fn main() {
7584        ˇ    println!("hello there");
7585
7586            println!("around the");
7587            println!("world");
7588        }
7589        "#
7590        .unindent(),
7591    );
7592
7593    cx.update_editor(|editor, cx| {
7594        editor.fold(&Fold, cx);
7595
7596        //Make sure that the fold only gets one hunk
7597        for _ in 0..4 {
7598            editor.go_to_hunk(&GoToHunk, cx);
7599        }
7600    });
7601
7602    cx.assert_editor_state(
7603        &r#"
7604        ˇuse some::modified;
7605
7606
7607        fn main() {
7608            println!("hello there");
7609
7610            println!("around the");
7611            println!("world");
7612        }
7613        "#
7614        .unindent(),
7615    );
7616}
7617
7618#[test]
7619fn test_split_words() {
7620    fn split(text: &str) -> Vec<&str> {
7621        split_words(text).collect()
7622    }
7623
7624    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
7625    assert_eq!(split("hello_world"), &["hello_", "world"]);
7626    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
7627    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
7628    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
7629    assert_eq!(split("helloworld"), &["helloworld"]);
7630
7631    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
7632}
7633
7634#[gpui::test]
7635async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
7636    init_test(cx, |_| {});
7637
7638    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
7639    let mut assert = |before, after| {
7640        let _state_context = cx.set_state(before);
7641        cx.update_editor(|editor, cx| {
7642            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
7643        });
7644        cx.assert_editor_state(after);
7645    };
7646
7647    // Outside bracket jumps to outside of matching bracket
7648    assert("console.logˇ(var);", "console.log(var)ˇ;");
7649    assert("console.log(var)ˇ;", "console.logˇ(var);");
7650
7651    // Inside bracket jumps to inside of matching bracket
7652    assert("console.log(ˇvar);", "console.log(varˇ);");
7653    assert("console.log(varˇ);", "console.log(ˇvar);");
7654
7655    // When outside a bracket and inside, favor jumping to the inside bracket
7656    assert(
7657        "console.log('foo', [1, 2, 3]ˇ);",
7658        "console.log(ˇ'foo', [1, 2, 3]);",
7659    );
7660    assert(
7661        "console.log(ˇ'foo', [1, 2, 3]);",
7662        "console.log('foo', [1, 2, 3]ˇ);",
7663    );
7664
7665    // Bias forward if two options are equally likely
7666    assert(
7667        "let result = curried_fun()ˇ();",
7668        "let result = curried_fun()()ˇ;",
7669    );
7670
7671    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
7672    assert(
7673        indoc! {"
7674            function test() {
7675                console.log('test')ˇ
7676            }"},
7677        indoc! {"
7678            function test() {
7679                console.logˇ('test')
7680            }"},
7681    );
7682}
7683
7684#[gpui::test]
7685async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
7686    init_test(cx, |_| {});
7687
7688    let fs = FakeFs::new(cx.executor());
7689    fs.insert_tree(
7690        "/a",
7691        json!({
7692            "main.rs": "fn main() { let a = 5; }",
7693            "other.rs": "// Test file",
7694        }),
7695    )
7696    .await;
7697    let project = Project::test(fs, ["/a".as_ref()], cx).await;
7698
7699    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7700    language_registry.add(Arc::new(Language::new(
7701        LanguageConfig {
7702            name: "Rust".into(),
7703            matcher: LanguageMatcher {
7704                path_suffixes: vec!["rs".to_string()],
7705                ..Default::default()
7706            },
7707            brackets: BracketPairConfig {
7708                pairs: vec![BracketPair {
7709                    start: "{".to_string(),
7710                    end: "}".to_string(),
7711                    close: true,
7712                    newline: true,
7713                }],
7714                disabled_scopes_by_bracket_ix: Vec::new(),
7715            },
7716            ..Default::default()
7717        },
7718        Some(tree_sitter_rust::language()),
7719    )));
7720    let mut fake_servers = language_registry.register_fake_lsp_adapter(
7721        "Rust",
7722        FakeLspAdapter {
7723            capabilities: lsp::ServerCapabilities {
7724                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
7725                    first_trigger_character: "{".to_string(),
7726                    more_trigger_character: None,
7727                }),
7728                ..Default::default()
7729            },
7730            ..Default::default()
7731        },
7732    );
7733
7734    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7735
7736    let cx = &mut VisualTestContext::from_window(*workspace, cx);
7737
7738    let worktree_id = workspace
7739        .update(cx, |workspace, cx| {
7740            workspace.project().update(cx, |project, cx| {
7741                project.worktrees().next().unwrap().read(cx).id()
7742            })
7743        })
7744        .unwrap();
7745
7746    let buffer = project
7747        .update(cx, |project, cx| {
7748            project.open_local_buffer("/a/main.rs", cx)
7749        })
7750        .await
7751        .unwrap();
7752    cx.executor().run_until_parked();
7753    cx.executor().start_waiting();
7754    let fake_server = fake_servers.next().await.unwrap();
7755    let editor_handle = workspace
7756        .update(cx, |workspace, cx| {
7757            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
7758        })
7759        .unwrap()
7760        .await
7761        .unwrap()
7762        .downcast::<Editor>()
7763        .unwrap();
7764
7765    fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
7766        assert_eq!(
7767            params.text_document_position.text_document.uri,
7768            lsp::Url::from_file_path("/a/main.rs").unwrap(),
7769        );
7770        assert_eq!(
7771            params.text_document_position.position,
7772            lsp::Position::new(0, 21),
7773        );
7774
7775        Ok(Some(vec![lsp::TextEdit {
7776            new_text: "]".to_string(),
7777            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
7778        }]))
7779    });
7780
7781    editor_handle.update(cx, |editor, cx| {
7782        editor.focus(cx);
7783        editor.change_selections(None, cx, |s| {
7784            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
7785        });
7786        editor.handle_input("{", cx);
7787    });
7788
7789    cx.executor().run_until_parked();
7790
7791    _ = buffer.update(cx, |buffer, _| {
7792        assert_eq!(
7793            buffer.text(),
7794            "fn main() { let a = {5}; }",
7795            "No extra braces from on type formatting should appear in the buffer"
7796        )
7797    });
7798}
7799
7800#[gpui::test]
7801async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
7802    init_test(cx, |_| {});
7803
7804    let fs = FakeFs::new(cx.executor());
7805    fs.insert_tree(
7806        "/a",
7807        json!({
7808            "main.rs": "fn main() { let a = 5; }",
7809            "other.rs": "// Test file",
7810        }),
7811    )
7812    .await;
7813
7814    let project = Project::test(fs, ["/a".as_ref()], cx).await;
7815
7816    let server_restarts = Arc::new(AtomicUsize::new(0));
7817    let closure_restarts = Arc::clone(&server_restarts);
7818    let language_server_name = "test language server";
7819    let language_name: Arc<str> = "Rust".into();
7820
7821    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7822    language_registry.add(Arc::new(Language::new(
7823        LanguageConfig {
7824            name: Arc::clone(&language_name),
7825            matcher: LanguageMatcher {
7826                path_suffixes: vec!["rs".to_string()],
7827                ..Default::default()
7828            },
7829            ..Default::default()
7830        },
7831        Some(tree_sitter_rust::language()),
7832    )));
7833    let mut fake_servers = language_registry.register_fake_lsp_adapter(
7834        "Rust",
7835        FakeLspAdapter {
7836            name: language_server_name,
7837            initialization_options: Some(json!({
7838                "testOptionValue": true
7839            })),
7840            initializer: Some(Box::new(move |fake_server| {
7841                let task_restarts = Arc::clone(&closure_restarts);
7842                fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
7843                    task_restarts.fetch_add(1, atomic::Ordering::Release);
7844                    futures::future::ready(Ok(()))
7845                });
7846            })),
7847            ..Default::default()
7848        },
7849    );
7850
7851    let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7852    let _buffer = project
7853        .update(cx, |project, cx| {
7854            project.open_local_buffer("/a/main.rs", cx)
7855        })
7856        .await
7857        .unwrap();
7858    let _fake_server = fake_servers.next().await.unwrap();
7859    update_test_language_settings(cx, |language_settings| {
7860        language_settings.languages.insert(
7861            Arc::clone(&language_name),
7862            LanguageSettingsContent {
7863                tab_size: NonZeroU32::new(8),
7864                ..Default::default()
7865            },
7866        );
7867    });
7868    cx.executor().run_until_parked();
7869    assert_eq!(
7870        server_restarts.load(atomic::Ordering::Acquire),
7871        0,
7872        "Should not restart LSP server on an unrelated change"
7873    );
7874
7875    update_test_project_settings(cx, |project_settings| {
7876        project_settings.lsp.insert(
7877            "Some other server name".into(),
7878            LspSettings {
7879                binary: None,
7880                settings: None,
7881                initialization_options: Some(json!({
7882                    "some other init value": false
7883                })),
7884            },
7885        );
7886    });
7887    cx.executor().run_until_parked();
7888    assert_eq!(
7889        server_restarts.load(atomic::Ordering::Acquire),
7890        0,
7891        "Should not restart LSP server on an unrelated LSP settings change"
7892    );
7893
7894    update_test_project_settings(cx, |project_settings| {
7895        project_settings.lsp.insert(
7896            language_server_name.into(),
7897            LspSettings {
7898                binary: None,
7899                settings: None,
7900                initialization_options: Some(json!({
7901                    "anotherInitValue": false
7902                })),
7903            },
7904        );
7905    });
7906    cx.executor().run_until_parked();
7907    assert_eq!(
7908        server_restarts.load(atomic::Ordering::Acquire),
7909        1,
7910        "Should restart LSP server on a related LSP settings change"
7911    );
7912
7913    update_test_project_settings(cx, |project_settings| {
7914        project_settings.lsp.insert(
7915            language_server_name.into(),
7916            LspSettings {
7917                binary: None,
7918                settings: None,
7919                initialization_options: Some(json!({
7920                    "anotherInitValue": false
7921                })),
7922            },
7923        );
7924    });
7925    cx.executor().run_until_parked();
7926    assert_eq!(
7927        server_restarts.load(atomic::Ordering::Acquire),
7928        1,
7929        "Should not restart LSP server on a related LSP settings change that is the same"
7930    );
7931
7932    update_test_project_settings(cx, |project_settings| {
7933        project_settings.lsp.insert(
7934            language_server_name.into(),
7935            LspSettings {
7936                binary: None,
7937                settings: None,
7938                initialization_options: None,
7939            },
7940        );
7941    });
7942    cx.executor().run_until_parked();
7943    assert_eq!(
7944        server_restarts.load(atomic::Ordering::Acquire),
7945        2,
7946        "Should restart LSP server on another related LSP settings change"
7947    );
7948}
7949
7950#[gpui::test]
7951async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
7952    init_test(cx, |_| {});
7953
7954    let mut cx = EditorLspTestContext::new_rust(
7955        lsp::ServerCapabilities {
7956            completion_provider: Some(lsp::CompletionOptions {
7957                trigger_characters: Some(vec![".".to_string()]),
7958                resolve_provider: Some(true),
7959                ..Default::default()
7960            }),
7961            ..Default::default()
7962        },
7963        cx,
7964    )
7965    .await;
7966
7967    cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7968    cx.simulate_keystroke(".");
7969    let completion_item = lsp::CompletionItem {
7970        label: "some".into(),
7971        kind: Some(lsp::CompletionItemKind::SNIPPET),
7972        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7973        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7974            kind: lsp::MarkupKind::Markdown,
7975            value: "```rust\nSome(2)\n```".to_string(),
7976        })),
7977        deprecated: Some(false),
7978        sort_text: Some("fffffff2".to_string()),
7979        filter_text: Some("some".to_string()),
7980        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7981        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7982            range: lsp::Range {
7983                start: lsp::Position {
7984                    line: 0,
7985                    character: 22,
7986                },
7987                end: lsp::Position {
7988                    line: 0,
7989                    character: 22,
7990                },
7991            },
7992            new_text: "Some(2)".to_string(),
7993        })),
7994        additional_text_edits: Some(vec![lsp::TextEdit {
7995            range: lsp::Range {
7996                start: lsp::Position {
7997                    line: 0,
7998                    character: 20,
7999                },
8000                end: lsp::Position {
8001                    line: 0,
8002                    character: 22,
8003                },
8004            },
8005            new_text: "".to_string(),
8006        }]),
8007        ..Default::default()
8008    };
8009
8010    let closure_completion_item = completion_item.clone();
8011    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8012        let task_completion_item = closure_completion_item.clone();
8013        async move {
8014            Ok(Some(lsp::CompletionResponse::Array(vec![
8015                task_completion_item,
8016            ])))
8017        }
8018    });
8019
8020    request.next().await;
8021
8022    cx.condition(|editor, _| editor.context_menu_visible())
8023        .await;
8024    let apply_additional_edits = cx.update_editor(|editor, cx| {
8025        editor
8026            .confirm_completion(&ConfirmCompletion::default(), cx)
8027            .unwrap()
8028    });
8029    cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
8030
8031    cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
8032        let task_completion_item = completion_item.clone();
8033        async move { Ok(task_completion_item) }
8034    })
8035    .next()
8036    .await
8037    .unwrap();
8038    apply_additional_edits.await.unwrap();
8039    cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
8040}
8041
8042#[gpui::test]
8043async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
8044    init_test(cx, |_| {});
8045
8046    let mut cx = EditorLspTestContext::new(
8047        Language::new(
8048            LanguageConfig {
8049                matcher: LanguageMatcher {
8050                    path_suffixes: vec!["jsx".into()],
8051                    ..Default::default()
8052                },
8053                overrides: [(
8054                    "element".into(),
8055                    LanguageConfigOverride {
8056                        word_characters: Override::Set(['-'].into_iter().collect()),
8057                        ..Default::default()
8058                    },
8059                )]
8060                .into_iter()
8061                .collect(),
8062                ..Default::default()
8063            },
8064            Some(tree_sitter_typescript::language_tsx()),
8065        )
8066        .with_override_query("(jsx_self_closing_element) @element")
8067        .unwrap(),
8068        lsp::ServerCapabilities {
8069            completion_provider: Some(lsp::CompletionOptions {
8070                trigger_characters: Some(vec![":".to_string()]),
8071                ..Default::default()
8072            }),
8073            ..Default::default()
8074        },
8075        cx,
8076    )
8077    .await;
8078
8079    cx.lsp
8080        .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8081            Ok(Some(lsp::CompletionResponse::Array(vec![
8082                lsp::CompletionItem {
8083                    label: "bg-blue".into(),
8084                    ..Default::default()
8085                },
8086                lsp::CompletionItem {
8087                    label: "bg-red".into(),
8088                    ..Default::default()
8089                },
8090                lsp::CompletionItem {
8091                    label: "bg-yellow".into(),
8092                    ..Default::default()
8093                },
8094            ])))
8095        });
8096
8097    cx.set_state(r#"<p class="bgˇ" />"#);
8098
8099    // Trigger completion when typing a dash, because the dash is an extra
8100    // word character in the 'element' scope, which contains the cursor.
8101    cx.simulate_keystroke("-");
8102    cx.executor().run_until_parked();
8103    cx.update_editor(|editor, _| {
8104        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8105            assert_eq!(
8106                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8107                &["bg-red", "bg-blue", "bg-yellow"]
8108            );
8109        } else {
8110            panic!("expected completion menu to be open");
8111        }
8112    });
8113
8114    cx.simulate_keystroke("l");
8115    cx.executor().run_until_parked();
8116    cx.update_editor(|editor, _| {
8117        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8118            assert_eq!(
8119                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8120                &["bg-blue", "bg-yellow"]
8121            );
8122        } else {
8123            panic!("expected completion menu to be open");
8124        }
8125    });
8126
8127    // When filtering completions, consider the character after the '-' to
8128    // be the start of a subword.
8129    cx.set_state(r#"<p class="yelˇ" />"#);
8130    cx.simulate_keystroke("l");
8131    cx.executor().run_until_parked();
8132    cx.update_editor(|editor, _| {
8133        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8134            assert_eq!(
8135                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8136                &["bg-yellow"]
8137            );
8138        } else {
8139            panic!("expected completion menu to be open");
8140        }
8141    });
8142}
8143
8144#[gpui::test]
8145async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
8146    init_test(cx, |settings| {
8147        settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
8148    });
8149
8150    let fs = FakeFs::new(cx.executor());
8151    fs.insert_file("/file.rs", Default::default()).await;
8152
8153    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8154    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8155
8156    language_registry.add(Arc::new(Language::new(
8157        LanguageConfig {
8158            name: "Rust".into(),
8159            matcher: LanguageMatcher {
8160                path_suffixes: vec!["rs".to_string()],
8161                ..Default::default()
8162            },
8163            prettier_parser_name: Some("test_parser".to_string()),
8164            ..Default::default()
8165        },
8166        Some(tree_sitter_rust::language()),
8167    )));
8168
8169    let test_plugin = "test_plugin";
8170    let _ = language_registry.register_fake_lsp_adapter(
8171        "Rust",
8172        FakeLspAdapter {
8173            prettier_plugins: vec![test_plugin],
8174            ..Default::default()
8175        },
8176    );
8177
8178    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
8179    let buffer = project
8180        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
8181        .await
8182        .unwrap();
8183
8184    let buffer_text = "one\ntwo\nthree\n";
8185    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
8186    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8187    _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
8188
8189    editor
8190        .update(cx, |editor, cx| {
8191            editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8192        })
8193        .unwrap()
8194        .await;
8195    assert_eq!(
8196        editor.update(cx, |editor, cx| editor.text(cx)),
8197        buffer_text.to_string() + prettier_format_suffix,
8198        "Test prettier formatting was not applied to the original buffer text",
8199    );
8200
8201    update_test_language_settings(cx, |settings| {
8202        settings.defaults.formatter = Some(language_settings::Formatter::Auto)
8203    });
8204    let format = editor.update(cx, |editor, cx| {
8205        editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8206    });
8207    format.await.unwrap();
8208    assert_eq!(
8209        editor.update(cx, |editor, cx| editor.text(cx)),
8210        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
8211        "Autoformatting (via test prettier) was not applied to the original buffer text",
8212    );
8213}
8214
8215#[gpui::test]
8216async fn test_find_all_references(cx: &mut gpui::TestAppContext) {
8217    init_test(cx, |_| {});
8218
8219    let mut cx = EditorLspTestContext::new_rust(
8220        lsp::ServerCapabilities {
8221            document_formatting_provider: Some(lsp::OneOf::Left(true)),
8222            ..Default::default()
8223        },
8224        cx,
8225    )
8226    .await;
8227
8228    cx.set_state(indoc! {"
8229        fn foo(«paramˇ»: i64) {
8230            println!(param);
8231        }
8232    "});
8233
8234    cx.lsp
8235        .handle_request::<lsp::request::References, _, _>(move |_, _| async move {
8236            Ok(Some(vec![
8237                lsp::Location {
8238                    uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
8239                    range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 12)),
8240                },
8241                lsp::Location {
8242                    uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
8243                    range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 18)),
8244                },
8245            ]))
8246        });
8247
8248    let references = cx
8249        .update_editor(|editor, cx| editor.find_all_references(&FindAllReferences, cx))
8250        .unwrap();
8251
8252    cx.executor().run_until_parked();
8253
8254    cx.executor().start_waiting();
8255    references.await.unwrap();
8256
8257    cx.assert_editor_state(indoc! {"
8258        fn foo(param: i64) {
8259            println!(«paramˇ»);
8260        }
8261    "});
8262
8263    let references = cx
8264        .update_editor(|editor, cx| editor.find_all_references(&FindAllReferences, cx))
8265        .unwrap();
8266
8267    cx.executor().run_until_parked();
8268
8269    cx.executor().start_waiting();
8270    references.await.unwrap();
8271
8272    cx.assert_editor_state(indoc! {"
8273        fn foo(«paramˇ»: i64) {
8274            println!(param);
8275        }
8276    "});
8277
8278    cx.set_state(indoc! {"
8279        fn foo(param: i64) {
8280            let a = param;
8281            let aˇ = param;
8282            let a = param;
8283            println!(param);
8284        }
8285    "});
8286
8287    cx.lsp
8288        .handle_request::<lsp::request::References, _, _>(move |_, _| async move {
8289            Ok(Some(vec![lsp::Location {
8290                uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
8291                range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 9)),
8292            }]))
8293        });
8294
8295    let references = cx
8296        .update_editor(|editor, cx| editor.find_all_references(&FindAllReferences, cx))
8297        .unwrap();
8298
8299    cx.executor().run_until_parked();
8300
8301    cx.executor().start_waiting();
8302    references.await.unwrap();
8303
8304    cx.assert_editor_state(indoc! {"
8305        fn foo(param: i64) {
8306            let a = param;
8307            let «aˇ» = param;
8308            let a = param;
8309            println!(param);
8310        }
8311    "});
8312}
8313
8314#[gpui::test]
8315async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
8316    init_test(cx, |_| {});
8317    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
8318    let base_text = indoc! {r#"struct Row;
8319struct Row1;
8320struct Row2;
8321
8322struct Row4;
8323struct Row5;
8324struct Row6;
8325
8326struct Row8;
8327struct Row9;
8328struct Row10;"#};
8329
8330    // When addition hunks are not adjacent to carets, no hunk revert is performed
8331    assert_hunk_revert(
8332        indoc! {r#"struct Row;
8333                   struct Row1;
8334                   struct Row1.1;
8335                   struct Row1.2;
8336                   struct Row2;ˇ
8337
8338                   struct Row4;
8339                   struct Row5;
8340                   struct Row6;
8341
8342                   struct Row8;
8343                   ˇstruct Row9;
8344                   struct Row9.1;
8345                   struct Row9.2;
8346                   struct Row9.3;
8347                   struct Row10;"#},
8348        vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
8349        indoc! {r#"struct Row;
8350                   struct Row1;
8351                   struct Row1.1;
8352                   struct Row1.2;
8353                   struct Row2;ˇ
8354
8355                   struct Row4;
8356                   struct Row5;
8357                   struct Row6;
8358
8359                   struct Row8;
8360                   ˇstruct Row9;
8361                   struct Row9.1;
8362                   struct Row9.2;
8363                   struct Row9.3;
8364                   struct Row10;"#},
8365        base_text,
8366        &mut cx,
8367    );
8368    // Same for selections
8369    assert_hunk_revert(
8370        indoc! {r#"struct Row;
8371                   struct Row1;
8372                   struct Row2;
8373                   struct Row2.1;
8374                   struct Row2.2;
8375                   «ˇ
8376                   struct Row4;
8377                   struct» Row5;
8378                   «struct Row6;
8379                   ˇ»
8380                   struct Row9.1;
8381                   struct Row9.2;
8382                   struct Row9.3;
8383                   struct Row8;
8384                   struct Row9;
8385                   struct Row10;"#},
8386        vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
8387        indoc! {r#"struct Row;
8388                   struct Row1;
8389                   struct Row2;
8390                   struct Row2.1;
8391                   struct Row2.2;
8392                   «ˇ
8393                   struct Row4;
8394                   struct» Row5;
8395                   «struct Row6;
8396                   ˇ»
8397                   struct Row9.1;
8398                   struct Row9.2;
8399                   struct Row9.3;
8400                   struct Row8;
8401                   struct Row9;
8402                   struct Row10;"#},
8403        base_text,
8404        &mut cx,
8405    );
8406
8407    // When carets and selections intersect the addition hunks, those are reverted.
8408    // Adjacent carets got merged.
8409    assert_hunk_revert(
8410        indoc! {r#"struct Row;
8411                   ˇ// something on the top
8412                   struct Row1;
8413                   struct Row2;
8414                   struct Roˇw3.1;
8415                   struct Row2.2;
8416                   struct Row2.3;ˇ
8417
8418                   struct Row4;
8419                   struct ˇRow5.1;
8420                   struct Row5.2;
8421                   struct «Rowˇ»5.3;
8422                   struct Row5;
8423                   struct Row6;
8424                   ˇ
8425                   struct Row9.1;
8426                   struct «Rowˇ»9.2;
8427                   struct «ˇRow»9.3;
8428                   struct Row8;
8429                   struct Row9;
8430                   «ˇ// something on bottom»
8431                   struct Row10;"#},
8432        vec![
8433            DiffHunkStatus::Added,
8434            DiffHunkStatus::Added,
8435            DiffHunkStatus::Added,
8436            DiffHunkStatus::Added,
8437            DiffHunkStatus::Added,
8438        ],
8439        indoc! {r#"struct Row;
8440                   ˇstruct Row1;
8441                   struct Row2;
8442                   ˇ
8443                   struct Row4;
8444                   ˇstruct Row5;
8445                   struct Row6;
8446                   ˇ
8447                   ˇstruct Row8;
8448                   struct Row9;
8449                   ˇstruct Row10;"#},
8450        base_text,
8451        &mut cx,
8452    );
8453}
8454
8455#[gpui::test]
8456async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
8457    init_test(cx, |_| {});
8458    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
8459    let base_text = indoc! {r#"struct Row;
8460struct Row1;
8461struct Row2;
8462
8463struct Row4;
8464struct Row5;
8465struct Row6;
8466
8467struct Row8;
8468struct Row9;
8469struct Row10;"#};
8470
8471    // Modification hunks behave the same as the addition ones.
8472    assert_hunk_revert(
8473        indoc! {r#"struct Row;
8474                   struct Row1;
8475                   struct Row33;
8476                   ˇ
8477                   struct Row4;
8478                   struct Row5;
8479                   struct Row6;
8480                   ˇ
8481                   struct Row99;
8482                   struct Row9;
8483                   struct Row10;"#},
8484        vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
8485        indoc! {r#"struct Row;
8486                   struct Row1;
8487                   struct Row33;
8488                   ˇ
8489                   struct Row4;
8490                   struct Row5;
8491                   struct Row6;
8492                   ˇ
8493                   struct Row99;
8494                   struct Row9;
8495                   struct Row10;"#},
8496        base_text,
8497        &mut cx,
8498    );
8499    assert_hunk_revert(
8500        indoc! {r#"struct Row;
8501                   struct Row1;
8502                   struct Row33;
8503                   «ˇ
8504                   struct Row4;
8505                   struct» Row5;
8506                   «struct Row6;
8507                   ˇ»
8508                   struct Row99;
8509                   struct Row9;
8510                   struct Row10;"#},
8511        vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
8512        indoc! {r#"struct Row;
8513                   struct Row1;
8514                   struct Row33;
8515                   «ˇ
8516                   struct Row4;
8517                   struct» Row5;
8518                   «struct Row6;
8519                   ˇ»
8520                   struct Row99;
8521                   struct Row9;
8522                   struct Row10;"#},
8523        base_text,
8524        &mut cx,
8525    );
8526
8527    assert_hunk_revert(
8528        indoc! {r#"ˇstruct Row1.1;
8529                   struct Row1;
8530                   «ˇstr»uct Row22;
8531
8532                   struct ˇRow44;
8533                   struct Row5;
8534                   struct «Rˇ»ow66;ˇ
8535
8536                   «struˇ»ct Row88;
8537                   struct Row9;
8538                   struct Row1011;ˇ"#},
8539        vec![
8540            DiffHunkStatus::Modified,
8541            DiffHunkStatus::Modified,
8542            DiffHunkStatus::Modified,
8543            DiffHunkStatus::Modified,
8544            DiffHunkStatus::Modified,
8545            DiffHunkStatus::Modified,
8546        ],
8547        indoc! {r#"struct Row;
8548                   ˇstruct Row1;
8549                   struct Row2;
8550                   ˇ
8551                   struct Row4;
8552                   ˇstruct Row5;
8553                   struct Row6;
8554                   ˇ
8555                   struct Row8;
8556                   ˇstruct Row9;
8557                   struct Row10;ˇ"#},
8558        base_text,
8559        &mut cx,
8560    );
8561}
8562
8563#[gpui::test]
8564async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
8565    init_test(cx, |_| {});
8566    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
8567    let base_text = indoc! {r#"struct Row;
8568struct Row1;
8569struct Row2;
8570
8571struct Row4;
8572struct Row5;
8573struct Row6;
8574
8575struct Row8;
8576struct Row9;
8577struct Row10;"#};
8578
8579    // Deletion hunks trigger with carets on ajacent rows, so carets and selections have to stay farther to avoid the revert
8580    assert_hunk_revert(
8581        indoc! {r#"struct Row;
8582                   struct Row2;
8583
8584                   ˇstruct Row4;
8585                   struct Row5;
8586                   struct Row6;
8587                   ˇ
8588                   struct Row8;
8589                   struct Row10;"#},
8590        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
8591        indoc! {r#"struct Row;
8592                   struct Row2;
8593
8594                   ˇstruct Row4;
8595                   struct Row5;
8596                   struct Row6;
8597                   ˇ
8598                   struct Row8;
8599                   struct Row10;"#},
8600        base_text,
8601        &mut cx,
8602    );
8603    assert_hunk_revert(
8604        indoc! {r#"struct Row;
8605                   struct Row2;
8606
8607                   «ˇstruct Row4;
8608                   struct» Row5;
8609                   «struct Row6;
8610                   ˇ»
8611                   struct Row8;
8612                   struct Row10;"#},
8613        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
8614        indoc! {r#"struct Row;
8615                   struct Row2;
8616
8617                   «ˇstruct Row4;
8618                   struct» Row5;
8619                   «struct Row6;
8620                   ˇ»
8621                   struct Row8;
8622                   struct Row10;"#},
8623        base_text,
8624        &mut cx,
8625    );
8626
8627    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
8628    assert_hunk_revert(
8629        indoc! {r#"struct Row;
8630                   ˇstruct Row2;
8631
8632                   struct Row4;
8633                   struct Row5;
8634                   struct Row6;
8635
8636                   struct Row8;ˇ
8637                   struct Row10;"#},
8638        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
8639        indoc! {r#"struct Row;
8640                   struct Row1;
8641                   ˇstruct Row2;
8642
8643                   struct Row4;
8644                   struct Row5;
8645                   struct Row6;
8646
8647                   struct Row8;ˇ
8648                   struct Row9;
8649                   struct Row10;"#},
8650        base_text,
8651        &mut cx,
8652    );
8653    assert_hunk_revert(
8654        indoc! {r#"struct Row;
8655                   struct Row2«ˇ;
8656                   struct Row4;
8657                   struct» Row5;
8658                   «struct Row6;
8659
8660                   struct Row8;ˇ»
8661                   struct Row10;"#},
8662        vec![
8663            DiffHunkStatus::Removed,
8664            DiffHunkStatus::Removed,
8665            DiffHunkStatus::Removed,
8666        ],
8667        indoc! {r#"struct Row;
8668                   struct Row1;
8669                   struct Row2«ˇ;
8670
8671                   struct Row4;
8672                   struct» Row5;
8673                   «struct Row6;
8674
8675                   struct Row8;ˇ»
8676                   struct Row9;
8677                   struct Row10;"#},
8678        base_text,
8679        &mut cx,
8680    );
8681}
8682
8683#[gpui::test]
8684async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
8685    init_test(cx, |_| {});
8686
8687    let cols = 4;
8688    let rows = 10;
8689    let sample_text_1 = sample_text(rows, cols, 'a');
8690    assert_eq!(
8691        sample_text_1,
8692        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
8693    );
8694    let sample_text_2 = sample_text(rows, cols, 'l');
8695    assert_eq!(
8696        sample_text_2,
8697        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
8698    );
8699    let sample_text_3 = sample_text(rows, cols, 'v');
8700    assert_eq!(
8701        sample_text_3,
8702        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
8703    );
8704
8705    fn diff_every_buffer_row(
8706        buffer: &Model<Buffer>,
8707        sample_text: String,
8708        cols: usize,
8709        cx: &mut gpui::TestAppContext,
8710    ) {
8711        // revert first character in each row, creating one large diff hunk per buffer
8712        let is_first_char = |offset: usize| offset % cols == 0;
8713        buffer.update(cx, |buffer, cx| {
8714            buffer.set_text(
8715                sample_text
8716                    .chars()
8717                    .enumerate()
8718                    .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
8719                    .collect::<String>(),
8720                cx,
8721            );
8722            buffer.set_diff_base(Some(sample_text), cx);
8723        });
8724        cx.executor().run_until_parked();
8725    }
8726
8727    let buffer_1 = cx.new_model(|cx| {
8728        Buffer::new(
8729            0,
8730            BufferId::new(cx.entity_id().as_u64()).unwrap(),
8731            sample_text_1.clone(),
8732        )
8733    });
8734    diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
8735
8736    let buffer_2 = cx.new_model(|cx| {
8737        Buffer::new(
8738            1,
8739            BufferId::new(cx.entity_id().as_u64() + 1).unwrap(),
8740            sample_text_2.clone(),
8741        )
8742    });
8743    diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
8744
8745    let buffer_3 = cx.new_model(|cx| {
8746        Buffer::new(
8747            2,
8748            BufferId::new(cx.entity_id().as_u64() + 2).unwrap(),
8749            sample_text_3.clone(),
8750        )
8751    });
8752    diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
8753
8754    let multibuffer = cx.new_model(|cx| {
8755        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8756        multibuffer.push_excerpts(
8757            buffer_1.clone(),
8758            [
8759                ExcerptRange {
8760                    context: Point::new(0, 0)..Point::new(3, 0),
8761                    primary: None,
8762                },
8763                ExcerptRange {
8764                    context: Point::new(5, 0)..Point::new(7, 0),
8765                    primary: None,
8766                },
8767                ExcerptRange {
8768                    context: Point::new(9, 0)..Point::new(10, 4),
8769                    primary: None,
8770                },
8771            ],
8772            cx,
8773        );
8774        multibuffer.push_excerpts(
8775            buffer_2.clone(),
8776            [
8777                ExcerptRange {
8778                    context: Point::new(0, 0)..Point::new(3, 0),
8779                    primary: None,
8780                },
8781                ExcerptRange {
8782                    context: Point::new(5, 0)..Point::new(7, 0),
8783                    primary: None,
8784                },
8785                ExcerptRange {
8786                    context: Point::new(9, 0)..Point::new(10, 4),
8787                    primary: None,
8788                },
8789            ],
8790            cx,
8791        );
8792        multibuffer.push_excerpts(
8793            buffer_3.clone(),
8794            [
8795                ExcerptRange {
8796                    context: Point::new(0, 0)..Point::new(3, 0),
8797                    primary: None,
8798                },
8799                ExcerptRange {
8800                    context: Point::new(5, 0)..Point::new(7, 0),
8801                    primary: None,
8802                },
8803                ExcerptRange {
8804                    context: Point::new(9, 0)..Point::new(10, 4),
8805                    primary: None,
8806                },
8807            ],
8808            cx,
8809        );
8810        multibuffer
8811    });
8812
8813    let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8814    editor.update(cx, |editor, cx| {
8815        assert_eq!(editor.text(cx), "XaaaXbbbX\nccXc\ndXdd\n\nhXhh\nXiiiXjjjX\n\nXlllXmmmX\nnnXn\noXoo\n\nsXss\nXtttXuuuX\n\nXvvvXwwwX\nxxXx\nyXyy\n\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X\n");
8816        editor.select_all(&SelectAll, cx);
8817        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
8818    });
8819    cx.executor().run_until_parked();
8820    // When all ranges are selected, all buffer hunks are reverted.
8821    editor.update(cx, |editor, cx| {
8822        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");
8823    });
8824    buffer_1.update(cx, |buffer, _| {
8825        assert_eq!(buffer.text(), sample_text_1);
8826    });
8827    buffer_2.update(cx, |buffer, _| {
8828        assert_eq!(buffer.text(), sample_text_2);
8829    });
8830    buffer_3.update(cx, |buffer, _| {
8831        assert_eq!(buffer.text(), sample_text_3);
8832    });
8833
8834    diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
8835    diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
8836    diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
8837    editor.update(cx, |editor, cx| {
8838        editor.change_selections(None, cx, |s| {
8839            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
8840        });
8841        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
8842    });
8843    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
8844    // but not affect buffer_2 and its related excerpts.
8845    editor.update(cx, |editor, cx| {
8846        assert_eq!(
8847            editor.text(cx),
8848            "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX\n\n\nXvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X\n\n"
8849        );
8850    });
8851    buffer_1.update(cx, |buffer, _| {
8852        assert_eq!(buffer.text(), sample_text_1);
8853    });
8854    buffer_2.update(cx, |buffer, _| {
8855        assert_eq!(
8856            buffer.text(),
8857            "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
8858        );
8859    });
8860    buffer_3.update(cx, |buffer, _| {
8861        assert_eq!(
8862            buffer.text(),
8863            "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
8864        );
8865    });
8866}
8867
8868#[gpui::test]
8869async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
8870    init_test(cx, |_| {});
8871
8872    let cols = 4;
8873    let rows = 10;
8874    let sample_text_1 = sample_text(rows, cols, 'a');
8875    assert_eq!(
8876        sample_text_1,
8877        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
8878    );
8879    let sample_text_2 = sample_text(rows, cols, 'l');
8880    assert_eq!(
8881        sample_text_2,
8882        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
8883    );
8884    let sample_text_3 = sample_text(rows, cols, 'v');
8885    assert_eq!(
8886        sample_text_3,
8887        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
8888    );
8889
8890    let buffer_1 = cx.new_model(|cx| {
8891        Buffer::new(
8892            0,
8893            BufferId::new(cx.entity_id().as_u64()).unwrap(),
8894            sample_text_1.clone(),
8895        )
8896    });
8897
8898    let buffer_2 = cx.new_model(|cx| {
8899        Buffer::new(
8900            1,
8901            BufferId::new(cx.entity_id().as_u64() + 1).unwrap(),
8902            sample_text_2.clone(),
8903        )
8904    });
8905
8906    let buffer_3 = cx.new_model(|cx| {
8907        Buffer::new(
8908            2,
8909            BufferId::new(cx.entity_id().as_u64() + 2).unwrap(),
8910            sample_text_3.clone(),
8911        )
8912    });
8913
8914    let multi_buffer = cx.new_model(|cx| {
8915        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8916        multibuffer.push_excerpts(
8917            buffer_1.clone(),
8918            [
8919                ExcerptRange {
8920                    context: Point::new(0, 0)..Point::new(3, 0),
8921                    primary: None,
8922                },
8923                ExcerptRange {
8924                    context: Point::new(5, 0)..Point::new(7, 0),
8925                    primary: None,
8926                },
8927                ExcerptRange {
8928                    context: Point::new(9, 0)..Point::new(10, 4),
8929                    primary: None,
8930                },
8931            ],
8932            cx,
8933        );
8934        multibuffer.push_excerpts(
8935            buffer_2.clone(),
8936            [
8937                ExcerptRange {
8938                    context: Point::new(0, 0)..Point::new(3, 0),
8939                    primary: None,
8940                },
8941                ExcerptRange {
8942                    context: Point::new(5, 0)..Point::new(7, 0),
8943                    primary: None,
8944                },
8945                ExcerptRange {
8946                    context: Point::new(9, 0)..Point::new(10, 4),
8947                    primary: None,
8948                },
8949            ],
8950            cx,
8951        );
8952        multibuffer.push_excerpts(
8953            buffer_3.clone(),
8954            [
8955                ExcerptRange {
8956                    context: Point::new(0, 0)..Point::new(3, 0),
8957                    primary: None,
8958                },
8959                ExcerptRange {
8960                    context: Point::new(5, 0)..Point::new(7, 0),
8961                    primary: None,
8962                },
8963                ExcerptRange {
8964                    context: Point::new(9, 0)..Point::new(10, 4),
8965                    primary: None,
8966                },
8967            ],
8968            cx,
8969        );
8970        multibuffer
8971    });
8972
8973    let fs = FakeFs::new(cx.executor());
8974    fs.insert_tree(
8975        "/a",
8976        json!({
8977            "main.rs": sample_text_1,
8978            "other.rs": sample_text_2,
8979            "lib.rs": sample_text_3,
8980        }),
8981    )
8982    .await;
8983    let project = Project::test(fs, ["/a".as_ref()], cx).await;
8984    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8985    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
8986    let multi_buffer_editor =
8987        cx.new_view(|cx| Editor::new(EditorMode::Full, multi_buffer, Some(project.clone()), cx));
8988    let multibuffer_item_id = workspace
8989        .update(cx, |workspace, cx| {
8990            assert!(
8991                workspace.active_item(cx).is_none(),
8992                "active item should be None before the first item is added"
8993            );
8994            workspace.add_item_to_active_pane(Box::new(multi_buffer_editor.clone()), cx);
8995            let active_item = workspace
8996                .active_item(cx)
8997                .expect("should have an active item after adding the multi buffer");
8998            assert!(
8999                !active_item.is_singleton(cx),
9000                "A multi buffer was expected to active after adding"
9001            );
9002            active_item.item_id()
9003        })
9004        .unwrap();
9005    cx.executor().run_until_parked();
9006
9007    multi_buffer_editor.update(cx, |editor, cx| {
9008        editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
9009        editor.open_excerpts(&OpenExcerpts, cx);
9010    });
9011    cx.executor().run_until_parked();
9012    let first_item_id = workspace
9013        .update(cx, |workspace, cx| {
9014            let active_item = workspace
9015                .active_item(cx)
9016                .expect("should have an active item after navigating into the 1st buffer");
9017            let first_item_id = active_item.item_id();
9018            assert_ne!(
9019                first_item_id, multibuffer_item_id,
9020                "Should navigate into the 1st buffer and activate it"
9021            );
9022            assert!(
9023                active_item.is_singleton(cx),
9024                "New active item should be a singleton buffer"
9025            );
9026            assert_eq!(
9027                active_item
9028                    .act_as::<Editor>(cx)
9029                    .expect("should have navigated into an editor for the 1st buffer")
9030                    .read(cx)
9031                    .text(cx),
9032                sample_text_1
9033            );
9034
9035            workspace
9036                .go_back(workspace.active_pane().downgrade(), cx)
9037                .detach_and_log_err(cx);
9038
9039            first_item_id
9040        })
9041        .unwrap();
9042    cx.executor().run_until_parked();
9043    workspace
9044        .update(cx, |workspace, cx| {
9045            let active_item = workspace
9046                .active_item(cx)
9047                .expect("should have an active item after navigating back");
9048            assert_eq!(
9049                active_item.item_id(),
9050                multibuffer_item_id,
9051                "Should navigate back to the multi buffer"
9052            );
9053            assert!(!active_item.is_singleton(cx));
9054        })
9055        .unwrap();
9056
9057    multi_buffer_editor.update(cx, |editor, cx| {
9058        editor.change_selections(Some(Autoscroll::Next), cx, |s| {
9059            s.select_ranges(Some(39..40))
9060        });
9061        editor.open_excerpts(&OpenExcerpts, cx);
9062    });
9063    cx.executor().run_until_parked();
9064    let second_item_id = workspace
9065        .update(cx, |workspace, cx| {
9066            let active_item = workspace
9067                .active_item(cx)
9068                .expect("should have an active item after navigating into the 2nd buffer");
9069            let second_item_id = active_item.item_id();
9070            assert_ne!(
9071                second_item_id, multibuffer_item_id,
9072                "Should navigate away from the multibuffer"
9073            );
9074            assert_ne!(
9075                second_item_id, first_item_id,
9076                "Should navigate into the 2nd buffer and activate it"
9077            );
9078            assert!(
9079                active_item.is_singleton(cx),
9080                "New active item should be a singleton buffer"
9081            );
9082            assert_eq!(
9083                active_item
9084                    .act_as::<Editor>(cx)
9085                    .expect("should have navigated into an editor")
9086                    .read(cx)
9087                    .text(cx),
9088                sample_text_2
9089            );
9090
9091            workspace
9092                .go_back(workspace.active_pane().downgrade(), cx)
9093                .detach_and_log_err(cx);
9094
9095            second_item_id
9096        })
9097        .unwrap();
9098    cx.executor().run_until_parked();
9099    workspace
9100        .update(cx, |workspace, cx| {
9101            let active_item = workspace
9102                .active_item(cx)
9103                .expect("should have an active item after navigating back from the 2nd buffer");
9104            assert_eq!(
9105                active_item.item_id(),
9106                multibuffer_item_id,
9107                "Should navigate back from the 2nd buffer to the multi buffer"
9108            );
9109            assert!(!active_item.is_singleton(cx));
9110        })
9111        .unwrap();
9112
9113    multi_buffer_editor.update(cx, |editor, cx| {
9114        editor.change_selections(Some(Autoscroll::Next), cx, |s| {
9115            s.select_ranges(Some(60..70))
9116        });
9117        editor.open_excerpts(&OpenExcerpts, cx);
9118    });
9119    cx.executor().run_until_parked();
9120    workspace
9121        .update(cx, |workspace, cx| {
9122            let active_item = workspace
9123                .active_item(cx)
9124                .expect("should have an active item after navigating into the 3rd buffer");
9125            let third_item_id = active_item.item_id();
9126            assert_ne!(
9127                third_item_id, multibuffer_item_id,
9128                "Should navigate into the 3rd buffer and activate it"
9129            );
9130            assert_ne!(third_item_id, first_item_id);
9131            assert_ne!(third_item_id, second_item_id);
9132            assert!(
9133                active_item.is_singleton(cx),
9134                "New active item should be a singleton buffer"
9135            );
9136            assert_eq!(
9137                active_item
9138                    .act_as::<Editor>(cx)
9139                    .expect("should have navigated into an editor")
9140                    .read(cx)
9141                    .text(cx),
9142                sample_text_3
9143            );
9144
9145            workspace
9146                .go_back(workspace.active_pane().downgrade(), cx)
9147                .detach_and_log_err(cx);
9148        })
9149        .unwrap();
9150    cx.executor().run_until_parked();
9151    workspace
9152        .update(cx, |workspace, cx| {
9153            let active_item = workspace
9154                .active_item(cx)
9155                .expect("should have an active item after navigating back from the 3rd buffer");
9156            assert_eq!(
9157                active_item.item_id(),
9158                multibuffer_item_id,
9159                "Should navigate back from the 3rd buffer to the multi buffer"
9160            );
9161            assert!(!active_item.is_singleton(cx));
9162        })
9163        .unwrap();
9164}
9165
9166fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
9167    let point = DisplayPoint::new(row as u32, column as u32);
9168    point..point
9169}
9170
9171fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
9172    let (text, ranges) = marked_text_ranges(marked_text, true);
9173    assert_eq!(view.text(cx), text);
9174    assert_eq!(
9175        view.selections.ranges(cx),
9176        ranges,
9177        "Assert selections are {}",
9178        marked_text
9179    );
9180}
9181
9182/// Handle completion request passing a marked string specifying where the completion
9183/// should be triggered from using '|' character, what range should be replaced, and what completions
9184/// should be returned using '<' and '>' to delimit the range
9185pub fn handle_completion_request(
9186    cx: &mut EditorLspTestContext,
9187    marked_string: &str,
9188    completions: Vec<&'static str>,
9189) -> impl Future<Output = ()> {
9190    let complete_from_marker: TextRangeMarker = '|'.into();
9191    let replace_range_marker: TextRangeMarker = ('<', '>').into();
9192    let (_, mut marked_ranges) = marked_text_ranges_by(
9193        marked_string,
9194        vec![complete_from_marker.clone(), replace_range_marker.clone()],
9195    );
9196
9197    let complete_from_position =
9198        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
9199    let replace_range =
9200        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
9201
9202    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
9203        let completions = completions.clone();
9204        async move {
9205            assert_eq!(params.text_document_position.text_document.uri, url.clone());
9206            assert_eq!(
9207                params.text_document_position.position,
9208                complete_from_position
9209            );
9210            Ok(Some(lsp::CompletionResponse::Array(
9211                completions
9212                    .iter()
9213                    .map(|completion_text| lsp::CompletionItem {
9214                        label: completion_text.to_string(),
9215                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9216                            range: replace_range,
9217                            new_text: completion_text.to_string(),
9218                        })),
9219                        ..Default::default()
9220                    })
9221                    .collect(),
9222            )))
9223        }
9224    });
9225
9226    async move {
9227        request.next().await;
9228    }
9229}
9230
9231fn handle_resolve_completion_request(
9232    cx: &mut EditorLspTestContext,
9233    edits: Option<Vec<(&'static str, &'static str)>>,
9234) -> impl Future<Output = ()> {
9235    let edits = edits.map(|edits| {
9236        edits
9237            .iter()
9238            .map(|(marked_string, new_text)| {
9239                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
9240                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
9241                lsp::TextEdit::new(replace_range, new_text.to_string())
9242            })
9243            .collect::<Vec<_>>()
9244    });
9245
9246    let mut request =
9247        cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
9248            let edits = edits.clone();
9249            async move {
9250                Ok(lsp::CompletionItem {
9251                    additional_text_edits: edits,
9252                    ..Default::default()
9253                })
9254            }
9255        });
9256
9257    async move {
9258        request.next().await;
9259    }
9260}
9261
9262pub(crate) fn update_test_language_settings(
9263    cx: &mut TestAppContext,
9264    f: impl Fn(&mut AllLanguageSettingsContent),
9265) {
9266    _ = cx.update(|cx| {
9267        cx.update_global(|store: &mut SettingsStore, cx| {
9268            store.update_user_settings::<AllLanguageSettings>(cx, f);
9269        });
9270    });
9271}
9272
9273pub(crate) fn update_test_project_settings(
9274    cx: &mut TestAppContext,
9275    f: impl Fn(&mut ProjectSettings),
9276) {
9277    _ = cx.update(|cx| {
9278        cx.update_global(|store: &mut SettingsStore, cx| {
9279            store.update_user_settings::<ProjectSettings>(cx, f);
9280        });
9281    });
9282}
9283
9284pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
9285    _ = cx.update(|cx| {
9286        let store = SettingsStore::test(cx);
9287        cx.set_global(store);
9288        theme::init(theme::LoadThemes::JustBase, cx);
9289        release_channel::init("0.0.0", cx);
9290        client::init_settings(cx);
9291        language::init(cx);
9292        Project::init_settings(cx);
9293        workspace::init_settings(cx);
9294        crate::init(cx);
9295    });
9296
9297    update_test_language_settings(cx, f);
9298}
9299
9300pub(crate) fn rust_lang() -> Arc<Language> {
9301    Arc::new(Language::new(
9302        LanguageConfig {
9303            name: "Rust".into(),
9304            matcher: LanguageMatcher {
9305                path_suffixes: vec!["rs".to_string()],
9306                ..Default::default()
9307            },
9308            ..Default::default()
9309        },
9310        Some(tree_sitter_rust::language()),
9311    ))
9312}
9313
9314#[track_caller]
9315fn assert_hunk_revert(
9316    not_reverted_text_with_selections: &str,
9317    expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
9318    expected_reverted_text_with_selections: &str,
9319    base_text: &str,
9320    cx: &mut EditorLspTestContext,
9321) {
9322    cx.set_state(not_reverted_text_with_selections);
9323    cx.update_editor(|editor, cx| {
9324        editor
9325            .buffer()
9326            .read(cx)
9327            .as_singleton()
9328            .unwrap()
9329            .update(cx, |buffer, cx| {
9330                buffer.set_diff_base(Some(base_text.to_string()), cx);
9331            });
9332    });
9333    cx.executor().run_until_parked();
9334
9335    let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
9336        let snapshot = editor
9337            .buffer()
9338            .read(cx)
9339            .as_singleton()
9340            .unwrap()
9341            .read(cx)
9342            .snapshot();
9343        let reverted_hunk_statuses = snapshot
9344            .git_diff_hunks_in_row_range(0..u32::MAX)
9345            .map(|hunk| hunk.status())
9346            .collect::<Vec<_>>();
9347
9348        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
9349        reverted_hunk_statuses
9350    });
9351    cx.executor().run_until_parked();
9352    cx.assert_editor_state(expected_reverted_text_with_selections);
9353    assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
9354}