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_down(&DuplicateLineDown, 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_down(&DuplicateLineDown, 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_up(&DuplicateLineUp, 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_up(&DuplicateLineUp, 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: Box::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_multibuffer(cx: &mut gpui::TestAppContext) {
4092    init_test(cx, |_| {});
4093
4094    let mut cx = EditorTestContext::new_multibuffer(
4095        cx,
4096        [
4097            indoc! {
4098                "aaa\n«bbb\nccc\n»ddd"
4099            },
4100            indoc! {
4101                "aaa\n«bbb\nccc\n»ddd"
4102            },
4103        ],
4104    );
4105
4106    cx.assert_editor_state(indoc! {"
4107        ˇbbb
4108        ccc
4109
4110        bbb
4111        ccc
4112        "});
4113    cx.dispatch_action(SelectPrevious::default());
4114    cx.assert_editor_state(indoc! {"
4115                «bbbˇ»
4116                ccc
4117
4118                bbb
4119                ccc
4120                "});
4121    cx.dispatch_action(SelectPrevious::default());
4122    cx.assert_editor_state(indoc! {"
4123                «bbbˇ»
4124                ccc
4125
4126                «bbbˇ»
4127                ccc
4128                "});
4129}
4130
4131#[gpui::test]
4132async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
4133    init_test(cx, |_| {});
4134
4135    let mut cx = EditorTestContext::new(cx).await;
4136    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4137
4138    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4139        .unwrap();
4140    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4141
4142    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4143        .unwrap();
4144    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4145
4146    cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4147    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4148
4149    cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4150    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4151
4152    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4153        .unwrap();
4154    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
4155
4156    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4157        .unwrap();
4158    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
4159
4160    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4161        .unwrap();
4162    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4163}
4164
4165#[gpui::test]
4166async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4167    init_test(cx, |_| {});
4168
4169    let mut cx = EditorTestContext::new(cx).await;
4170    cx.set_state(
4171        r#"let foo = 2;
4172lˇet foo = 2;
4173let fooˇ = 2;
4174let foo = 2;
4175let foo = ˇ2;"#,
4176    );
4177
4178    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4179        .unwrap();
4180    cx.assert_editor_state(
4181        r#"let foo = 2;
4182«letˇ» foo = 2;
4183let «fooˇ» = 2;
4184let foo = 2;
4185let foo = «2ˇ»;"#,
4186    );
4187
4188    // noop for multiple selections with different contents
4189    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4190        .unwrap();
4191    cx.assert_editor_state(
4192        r#"let foo = 2;
4193«letˇ» foo = 2;
4194let «fooˇ» = 2;
4195let foo = 2;
4196let foo = «2ˇ»;"#,
4197    );
4198}
4199
4200#[gpui::test]
4201async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
4202    init_test(cx, |_| {});
4203
4204    let mut cx = EditorTestContext::new(cx).await;
4205    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
4206
4207    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4208        .unwrap();
4209    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4210
4211    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4212        .unwrap();
4213    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4214
4215    cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4216    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4217
4218    cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4219    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4220
4221    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4222        .unwrap();
4223    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
4224
4225    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4226        .unwrap();
4227    cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4228}
4229
4230#[gpui::test]
4231async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
4232    init_test(cx, |_| {});
4233
4234    let language = Arc::new(Language::new(
4235        LanguageConfig::default(),
4236        Some(tree_sitter_rust::language()),
4237    ));
4238
4239    let text = r#"
4240        use mod1::mod2::{mod3, mod4};
4241
4242        fn fn_1(param1: bool, param2: &str) {
4243            let var1 = "text";
4244        }
4245    "#
4246    .unindent();
4247
4248    let buffer = cx.new_model(|cx| {
4249        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
4250            .with_language(language, cx)
4251    });
4252    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4253    let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4254
4255    view.condition::<crate::EditorEvent>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4256        .await;
4257
4258    _ = view.update(cx, |view, cx| {
4259        view.change_selections(None, cx, |s| {
4260            s.select_display_ranges([
4261                DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
4262                DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
4263                DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
4264            ]);
4265        });
4266        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4267    });
4268    assert_eq!(
4269        view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
4270        &[
4271            DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
4272            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
4273            DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
4274        ]
4275    );
4276
4277    _ = view.update(cx, |view, cx| {
4278        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4279    });
4280    assert_eq!(
4281        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4282        &[
4283            DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
4284            DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
4285        ]
4286    );
4287
4288    _ = view.update(cx, |view, cx| {
4289        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4290    });
4291    assert_eq!(
4292        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4293        &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
4294    );
4295
4296    // Trying to expand the selected syntax node one more time has no effect.
4297    _ = view.update(cx, |view, cx| {
4298        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4299    });
4300    assert_eq!(
4301        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4302        &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
4303    );
4304
4305    _ = view.update(cx, |view, cx| {
4306        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4307    });
4308    assert_eq!(
4309        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4310        &[
4311            DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
4312            DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
4313        ]
4314    );
4315
4316    _ = view.update(cx, |view, cx| {
4317        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4318    });
4319    assert_eq!(
4320        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4321        &[
4322            DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
4323            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
4324            DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
4325        ]
4326    );
4327
4328    _ = view.update(cx, |view, cx| {
4329        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4330    });
4331    assert_eq!(
4332        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4333        &[
4334            DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
4335            DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
4336            DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
4337        ]
4338    );
4339
4340    // Trying to shrink the selected syntax node one more time has no effect.
4341    _ = view.update(cx, |view, cx| {
4342        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4343    });
4344    assert_eq!(
4345        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4346        &[
4347            DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
4348            DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
4349            DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
4350        ]
4351    );
4352
4353    // Ensure that we keep expanding the selection if the larger selection starts or ends within
4354    // a fold.
4355    _ = view.update(cx, |view, cx| {
4356        view.fold_ranges(
4357            vec![
4358                Point::new(0, 21)..Point::new(0, 24),
4359                Point::new(3, 20)..Point::new(3, 22),
4360            ],
4361            true,
4362            cx,
4363        );
4364        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4365    });
4366    assert_eq!(
4367        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4368        &[
4369            DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
4370            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
4371            DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
4372        ]
4373    );
4374}
4375
4376#[gpui::test]
4377async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
4378    init_test(cx, |_| {});
4379
4380    let language = Arc::new(
4381        Language::new(
4382            LanguageConfig {
4383                brackets: BracketPairConfig {
4384                    pairs: vec![
4385                        BracketPair {
4386                            start: "{".to_string(),
4387                            end: "}".to_string(),
4388                            close: false,
4389                            newline: true,
4390                        },
4391                        BracketPair {
4392                            start: "(".to_string(),
4393                            end: ")".to_string(),
4394                            close: false,
4395                            newline: true,
4396                        },
4397                    ],
4398                    ..Default::default()
4399                },
4400                ..Default::default()
4401            },
4402            Some(tree_sitter_rust::language()),
4403        )
4404        .with_indents_query(
4405            r#"
4406                (_ "(" ")" @end) @indent
4407                (_ "{" "}" @end) @indent
4408            "#,
4409        )
4410        .unwrap(),
4411    );
4412
4413    let text = "fn a() {}";
4414
4415    let buffer = cx.new_model(|cx| {
4416        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
4417            .with_language(language, cx)
4418    });
4419    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4420    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4421    editor
4422        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
4423        .await;
4424
4425    _ = editor.update(cx, |editor, cx| {
4426        editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
4427        editor.newline(&Newline, cx);
4428        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
4429        assert_eq!(
4430            editor.selections.ranges(cx),
4431            &[
4432                Point::new(1, 4)..Point::new(1, 4),
4433                Point::new(3, 4)..Point::new(3, 4),
4434                Point::new(5, 0)..Point::new(5, 0)
4435            ]
4436        );
4437    });
4438}
4439
4440#[gpui::test]
4441async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
4442    init_test(cx, |_| {});
4443
4444    let mut cx = EditorTestContext::new(cx).await;
4445
4446    let language = Arc::new(Language::new(
4447        LanguageConfig {
4448            brackets: BracketPairConfig {
4449                pairs: vec![
4450                    BracketPair {
4451                        start: "{".to_string(),
4452                        end: "}".to_string(),
4453                        close: true,
4454                        newline: true,
4455                    },
4456                    BracketPair {
4457                        start: "(".to_string(),
4458                        end: ")".to_string(),
4459                        close: true,
4460                        newline: true,
4461                    },
4462                    BracketPair {
4463                        start: "/*".to_string(),
4464                        end: " */".to_string(),
4465                        close: true,
4466                        newline: true,
4467                    },
4468                    BracketPair {
4469                        start: "[".to_string(),
4470                        end: "]".to_string(),
4471                        close: false,
4472                        newline: true,
4473                    },
4474                    BracketPair {
4475                        start: "\"".to_string(),
4476                        end: "\"".to_string(),
4477                        close: true,
4478                        newline: false,
4479                    },
4480                ],
4481                ..Default::default()
4482            },
4483            autoclose_before: "})]".to_string(),
4484            ..Default::default()
4485        },
4486        Some(tree_sitter_rust::language()),
4487    ));
4488
4489    cx.language_registry().add(language.clone());
4490    cx.update_buffer(|buffer, cx| {
4491        buffer.set_language(Some(language), cx);
4492    });
4493
4494    cx.set_state(
4495        &r#"
4496            🏀ˇ
4497            εˇ
4498            ❤️ˇ
4499        "#
4500        .unindent(),
4501    );
4502
4503    // autoclose multiple nested brackets at multiple cursors
4504    cx.update_editor(|view, cx| {
4505        view.handle_input("{", cx);
4506        view.handle_input("{", cx);
4507        view.handle_input("{", cx);
4508    });
4509    cx.assert_editor_state(
4510        &"
4511            🏀{{{ˇ}}}
4512            ε{{{ˇ}}}
4513            ❤️{{{ˇ}}}
4514        "
4515        .unindent(),
4516    );
4517
4518    // insert a different closing bracket
4519    cx.update_editor(|view, cx| {
4520        view.handle_input(")", cx);
4521    });
4522    cx.assert_editor_state(
4523        &"
4524            🏀{{{)ˇ}}}
4525            ε{{{)ˇ}}}
4526            ❤️{{{)ˇ}}}
4527        "
4528        .unindent(),
4529    );
4530
4531    // skip over the auto-closed brackets when typing a closing bracket
4532    cx.update_editor(|view, cx| {
4533        view.move_right(&MoveRight, cx);
4534        view.handle_input("}", cx);
4535        view.handle_input("}", cx);
4536        view.handle_input("}", cx);
4537    });
4538    cx.assert_editor_state(
4539        &"
4540            🏀{{{)}}}}ˇ
4541            ε{{{)}}}}ˇ
4542            ❤️{{{)}}}}ˇ
4543        "
4544        .unindent(),
4545    );
4546
4547    // autoclose multi-character pairs
4548    cx.set_state(
4549        &"
4550            ˇ
4551            ˇ
4552        "
4553        .unindent(),
4554    );
4555    cx.update_editor(|view, cx| {
4556        view.handle_input("/", cx);
4557        view.handle_input("*", cx);
4558    });
4559    cx.assert_editor_state(
4560        &"
4561            /*ˇ */
4562            /*ˇ */
4563        "
4564        .unindent(),
4565    );
4566
4567    // one cursor autocloses a multi-character pair, one cursor
4568    // does not autoclose.
4569    cx.set_state(
4570        &"
45714572            ˇ
4573        "
4574        .unindent(),
4575    );
4576    cx.update_editor(|view, cx| view.handle_input("*", cx));
4577    cx.assert_editor_state(
4578        &"
4579            /*ˇ */
45804581        "
4582        .unindent(),
4583    );
4584
4585    // Don't autoclose if the next character isn't whitespace and isn't
4586    // listed in the language's "autoclose_before" section.
4587    cx.set_state("ˇa b");
4588    cx.update_editor(|view, cx| view.handle_input("{", cx));
4589    cx.assert_editor_state("{ˇa b");
4590
4591    // Don't autoclose if `close` is false for the bracket pair
4592    cx.set_state("ˇ");
4593    cx.update_editor(|view, cx| view.handle_input("[", cx));
4594    cx.assert_editor_state("");
4595
4596    // Surround with brackets if text is selected
4597    cx.set_state("«aˇ» b");
4598    cx.update_editor(|view, cx| view.handle_input("{", cx));
4599    cx.assert_editor_state("{«aˇ»} b");
4600
4601    // Autclose pair where the start and end characters are the same
4602    cx.set_state("");
4603    cx.update_editor(|view, cx| view.handle_input("\"", cx));
4604    cx.assert_editor_state("a\"ˇ\"");
4605    cx.update_editor(|view, cx| view.handle_input("\"", cx));
4606    cx.assert_editor_state("a\"\"ˇ");
4607}
4608
4609#[gpui::test]
4610async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
4611    init_test(cx, |settings| {
4612        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
4613    });
4614
4615    let mut cx = EditorTestContext::new(cx).await;
4616
4617    let language = Arc::new(Language::new(
4618        LanguageConfig {
4619            brackets: BracketPairConfig {
4620                pairs: vec![
4621                    BracketPair {
4622                        start: "{".to_string(),
4623                        end: "}".to_string(),
4624                        close: true,
4625                        newline: true,
4626                    },
4627                    BracketPair {
4628                        start: "(".to_string(),
4629                        end: ")".to_string(),
4630                        close: true,
4631                        newline: true,
4632                    },
4633                    BracketPair {
4634                        start: "[".to_string(),
4635                        end: "]".to_string(),
4636                        close: false,
4637                        newline: true,
4638                    },
4639                ],
4640                ..Default::default()
4641            },
4642            autoclose_before: "})]".to_string(),
4643            ..Default::default()
4644        },
4645        Some(tree_sitter_rust::language()),
4646    ));
4647
4648    cx.language_registry().add(language.clone());
4649    cx.update_buffer(|buffer, cx| {
4650        buffer.set_language(Some(language), cx);
4651    });
4652
4653    cx.set_state(
4654        &"
4655            ˇ
4656            ˇ
4657            ˇ
4658        "
4659        .unindent(),
4660    );
4661
4662    // ensure only matching closing brackets are skipped over
4663    cx.update_editor(|view, cx| {
4664        view.handle_input("}", cx);
4665        view.move_left(&MoveLeft, cx);
4666        view.handle_input(")", cx);
4667        view.move_left(&MoveLeft, cx);
4668    });
4669    cx.assert_editor_state(
4670        &"
4671            ˇ)}
4672            ˇ)}
4673            ˇ)}
4674        "
4675        .unindent(),
4676    );
4677
4678    // skip-over closing brackets at multiple cursors
4679    cx.update_editor(|view, cx| {
4680        view.handle_input(")", cx);
4681        view.handle_input("}", cx);
4682    });
4683    cx.assert_editor_state(
4684        &"
4685            )}ˇ
4686            )}ˇ
4687            )}ˇ
4688        "
4689        .unindent(),
4690    );
4691
4692    // ignore non-close brackets
4693    cx.update_editor(|view, cx| {
4694        view.handle_input("]", cx);
4695        view.move_left(&MoveLeft, cx);
4696        view.handle_input("]", cx);
4697    });
4698    cx.assert_editor_state(
4699        &"
4700            )}]ˇ]
4701            )}]ˇ]
4702            )}]ˇ]
4703        "
4704        .unindent(),
4705    );
4706}
4707
4708#[gpui::test]
4709async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
4710    init_test(cx, |_| {});
4711
4712    let mut cx = EditorTestContext::new(cx).await;
4713
4714    let html_language = Arc::new(
4715        Language::new(
4716            LanguageConfig {
4717                name: "HTML".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_html::language()),
4745        )
4746        .with_injection_query(
4747            r#"
4748            (script_element
4749                (raw_text) @content
4750                (#set! "language" "javascript"))
4751            "#,
4752        )
4753        .unwrap(),
4754    );
4755
4756    let javascript_language = Arc::new(Language::new(
4757        LanguageConfig {
4758            name: "JavaScript".into(),
4759            brackets: BracketPairConfig {
4760                pairs: vec![
4761                    BracketPair {
4762                        start: "/*".into(),
4763                        end: " */".into(),
4764                        close: true,
4765                        ..Default::default()
4766                    },
4767                    BracketPair {
4768                        start: "{".into(),
4769                        end: "}".into(),
4770                        close: true,
4771                        ..Default::default()
4772                    },
4773                    BracketPair {
4774                        start: "(".into(),
4775                        end: ")".into(),
4776                        close: true,
4777                        ..Default::default()
4778                    },
4779                ],
4780                ..Default::default()
4781            },
4782            autoclose_before: "})]>".into(),
4783            ..Default::default()
4784        },
4785        Some(tree_sitter_typescript::language_tsx()),
4786    ));
4787
4788    cx.language_registry().add(html_language.clone());
4789    cx.language_registry().add(javascript_language.clone());
4790
4791    cx.update_buffer(|buffer, cx| {
4792        buffer.set_language(Some(html_language), cx);
4793    });
4794
4795    cx.set_state(
4796        &r#"
4797            <body>ˇ
4798                <script>
4799                    var x = 1;ˇ
4800                </script>
4801            </body>ˇ
4802        "#
4803        .unindent(),
4804    );
4805
4806    // Precondition: different languages are active at different locations.
4807    cx.update_editor(|editor, cx| {
4808        let snapshot = editor.snapshot(cx);
4809        let cursors = editor.selections.ranges::<usize>(cx);
4810        let languages = cursors
4811            .iter()
4812            .map(|c| snapshot.language_at(c.start).unwrap().name())
4813            .collect::<Vec<_>>();
4814        assert_eq!(
4815            languages,
4816            &["HTML".into(), "JavaScript".into(), "HTML".into()]
4817        );
4818    });
4819
4820    // Angle brackets autoclose in HTML, but not JavaScript.
4821    cx.update_editor(|editor, cx| {
4822        editor.handle_input("<", cx);
4823        editor.handle_input("a", cx);
4824    });
4825    cx.assert_editor_state(
4826        &r#"
4827            <body><aˇ>
4828                <script>
4829                    var x = 1;<aˇ
4830                </script>
4831            </body><aˇ>
4832        "#
4833        .unindent(),
4834    );
4835
4836    // Curly braces and parens autoclose in both HTML and JavaScript.
4837    cx.update_editor(|editor, cx| {
4838        editor.handle_input(" b=", cx);
4839        editor.handle_input("{", cx);
4840        editor.handle_input("c", cx);
4841        editor.handle_input("(", cx);
4842    });
4843    cx.assert_editor_state(
4844        &r#"
4845            <body><a b={c(ˇ)}>
4846                <script>
4847                    var x = 1;<a b={c(ˇ)}
4848                </script>
4849            </body><a b={c(ˇ)}>
4850        "#
4851        .unindent(),
4852    );
4853
4854    // Brackets that were already autoclosed are skipped.
4855    cx.update_editor(|editor, cx| {
4856        editor.handle_input(")", cx);
4857        editor.handle_input("d", cx);
4858        editor.handle_input("}", cx);
4859    });
4860    cx.assert_editor_state(
4861        &r#"
4862            <body><a b={c()d}ˇ>
4863                <script>
4864                    var x = 1;<a b={c()d}ˇ
4865                </script>
4866            </body><a b={c()d}ˇ>
4867        "#
4868        .unindent(),
4869    );
4870    cx.update_editor(|editor, cx| {
4871        editor.handle_input(">", cx);
4872    });
4873    cx.assert_editor_state(
4874        &r#"
4875            <body><a b={c()d}>ˇ
4876                <script>
4877                    var x = 1;<a b={c()d}>ˇ
4878                </script>
4879            </body><a b={c()d}>ˇ
4880        "#
4881        .unindent(),
4882    );
4883
4884    // Reset
4885    cx.set_state(
4886        &r#"
4887            <body>ˇ
4888                <script>
4889                    var x = 1;ˇ
4890                </script>
4891            </body>ˇ
4892        "#
4893        .unindent(),
4894    );
4895
4896    cx.update_editor(|editor, cx| {
4897        editor.handle_input("<", cx);
4898    });
4899    cx.assert_editor_state(
4900        &r#"
4901            <body><ˇ>
4902                <script>
4903                    var x = 1;<ˇ
4904                </script>
4905            </body><ˇ>
4906        "#
4907        .unindent(),
4908    );
4909
4910    // When backspacing, the closing angle brackets are removed.
4911    cx.update_editor(|editor, cx| {
4912        editor.backspace(&Backspace, cx);
4913    });
4914    cx.assert_editor_state(
4915        &r#"
4916            <body>ˇ
4917                <script>
4918                    var x = 1;ˇ
4919                </script>
4920            </body>ˇ
4921        "#
4922        .unindent(),
4923    );
4924
4925    // Block comments autoclose in JavaScript, but not HTML.
4926    cx.update_editor(|editor, cx| {
4927        editor.handle_input("/", cx);
4928        editor.handle_input("*", cx);
4929    });
4930    cx.assert_editor_state(
4931        &r#"
4932            <body>/*ˇ
4933                <script>
4934                    var x = 1;/*ˇ */
4935                </script>
4936            </body>/*ˇ
4937        "#
4938        .unindent(),
4939    );
4940}
4941
4942#[gpui::test]
4943async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
4944    init_test(cx, |_| {});
4945
4946    let mut cx = EditorTestContext::new(cx).await;
4947
4948    let rust_language = Arc::new(
4949        Language::new(
4950            LanguageConfig {
4951                name: "Rust".into(),
4952                brackets: serde_json::from_value(json!([
4953                    { "start": "{", "end": "}", "close": true, "newline": true },
4954                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
4955                ]))
4956                .unwrap(),
4957                autoclose_before: "})]>".into(),
4958                ..Default::default()
4959            },
4960            Some(tree_sitter_rust::language()),
4961        )
4962        .with_override_query("(string_literal) @string")
4963        .unwrap(),
4964    );
4965
4966    cx.language_registry().add(rust_language.clone());
4967    cx.update_buffer(|buffer, cx| {
4968        buffer.set_language(Some(rust_language), cx);
4969    });
4970
4971    cx.set_state(
4972        &r#"
4973            let x = ˇ
4974        "#
4975        .unindent(),
4976    );
4977
4978    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
4979    cx.update_editor(|editor, cx| {
4980        editor.handle_input("\"", cx);
4981    });
4982    cx.assert_editor_state(
4983        &r#"
4984            let x = "ˇ"
4985        "#
4986        .unindent(),
4987    );
4988
4989    // Inserting another quotation mark. The cursor moves across the existing
4990    // automatically-inserted quotation mark.
4991    cx.update_editor(|editor, cx| {
4992        editor.handle_input("\"", cx);
4993    });
4994    cx.assert_editor_state(
4995        &r#"
4996            let x = ""ˇ
4997        "#
4998        .unindent(),
4999    );
5000
5001    // Reset
5002    cx.set_state(
5003        &r#"
5004            let x = ˇ
5005        "#
5006        .unindent(),
5007    );
5008
5009    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
5010    cx.update_editor(|editor, cx| {
5011        editor.handle_input("\"", cx);
5012        editor.handle_input(" ", cx);
5013        editor.move_left(&Default::default(), cx);
5014        editor.handle_input("\\", cx);
5015        editor.handle_input("\"", cx);
5016    });
5017    cx.assert_editor_state(
5018        &r#"
5019            let x = "\"ˇ "
5020        "#
5021        .unindent(),
5022    );
5023
5024    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
5025    // mark. Nothing is inserted.
5026    cx.update_editor(|editor, cx| {
5027        editor.move_right(&Default::default(), cx);
5028        editor.handle_input("\"", cx);
5029    });
5030    cx.assert_editor_state(
5031        &r#"
5032            let x = "\" "ˇ
5033        "#
5034        .unindent(),
5035    );
5036}
5037
5038#[gpui::test]
5039async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
5040    init_test(cx, |_| {});
5041
5042    let language = Arc::new(Language::new(
5043        LanguageConfig {
5044            brackets: BracketPairConfig {
5045                pairs: vec![
5046                    BracketPair {
5047                        start: "{".to_string(),
5048                        end: "}".to_string(),
5049                        close: true,
5050                        newline: true,
5051                    },
5052                    BracketPair {
5053                        start: "/* ".to_string(),
5054                        end: "*/".to_string(),
5055                        close: true,
5056                        ..Default::default()
5057                    },
5058                ],
5059                ..Default::default()
5060            },
5061            ..Default::default()
5062        },
5063        Some(tree_sitter_rust::language()),
5064    ));
5065
5066    let text = r#"
5067        a
5068        b
5069        c
5070    "#
5071    .unindent();
5072
5073    let buffer = cx.new_model(|cx| {
5074        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
5075            .with_language(language, cx)
5076    });
5077    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5078    let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5079    view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5080        .await;
5081
5082    _ = view.update(cx, |view, cx| {
5083        view.change_selections(None, cx, |s| {
5084            s.select_display_ranges([
5085                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
5086                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
5087                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
5088            ])
5089        });
5090
5091        view.handle_input("{", cx);
5092        view.handle_input("{", cx);
5093        view.handle_input("{", cx);
5094        assert_eq!(
5095            view.text(cx),
5096            "
5097                {{{a}}}
5098                {{{b}}}
5099                {{{c}}}
5100            "
5101            .unindent()
5102        );
5103        assert_eq!(
5104            view.selections.display_ranges(cx),
5105            [
5106                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
5107                DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
5108                DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
5109            ]
5110        );
5111
5112        view.undo(&Undo, cx);
5113        view.undo(&Undo, cx);
5114        view.undo(&Undo, cx);
5115        assert_eq!(
5116            view.text(cx),
5117            "
5118                a
5119                b
5120                c
5121            "
5122            .unindent()
5123        );
5124        assert_eq!(
5125            view.selections.display_ranges(cx),
5126            [
5127                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
5128                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
5129                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
5130            ]
5131        );
5132
5133        // Ensure inserting the first character of a multi-byte bracket pair
5134        // doesn't surround the selections with the bracket.
5135        view.handle_input("/", cx);
5136        assert_eq!(
5137            view.text(cx),
5138            "
5139                /
5140                /
5141                /
5142            "
5143            .unindent()
5144        );
5145        assert_eq!(
5146            view.selections.display_ranges(cx),
5147            [
5148                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
5149                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
5150                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
5151            ]
5152        );
5153
5154        view.undo(&Undo, cx);
5155        assert_eq!(
5156            view.text(cx),
5157            "
5158                a
5159                b
5160                c
5161            "
5162            .unindent()
5163        );
5164        assert_eq!(
5165            view.selections.display_ranges(cx),
5166            [
5167                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
5168                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
5169                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
5170            ]
5171        );
5172
5173        // Ensure inserting the last character of a multi-byte bracket pair
5174        // doesn't surround the selections with the bracket.
5175        view.handle_input("*", cx);
5176        assert_eq!(
5177            view.text(cx),
5178            "
5179                *
5180                *
5181                *
5182            "
5183            .unindent()
5184        );
5185        assert_eq!(
5186            view.selections.display_ranges(cx),
5187            [
5188                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
5189                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
5190                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
5191            ]
5192        );
5193    });
5194}
5195
5196#[gpui::test]
5197async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
5198    init_test(cx, |_| {});
5199
5200    let language = Arc::new(Language::new(
5201        LanguageConfig {
5202            brackets: BracketPairConfig {
5203                pairs: vec![BracketPair {
5204                    start: "{".to_string(),
5205                    end: "}".to_string(),
5206                    close: true,
5207                    newline: true,
5208                }],
5209                ..Default::default()
5210            },
5211            autoclose_before: "}".to_string(),
5212            ..Default::default()
5213        },
5214        Some(tree_sitter_rust::language()),
5215    ));
5216
5217    let text = r#"
5218        a
5219        b
5220        c
5221    "#
5222    .unindent();
5223
5224    let buffer = cx.new_model(|cx| {
5225        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
5226            .with_language(language, cx)
5227    });
5228    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5229    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5230    editor
5231        .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5232        .await;
5233
5234    _ = editor.update(cx, |editor, cx| {
5235        editor.change_selections(None, cx, |s| {
5236            s.select_ranges([
5237                Point::new(0, 1)..Point::new(0, 1),
5238                Point::new(1, 1)..Point::new(1, 1),
5239                Point::new(2, 1)..Point::new(2, 1),
5240            ])
5241        });
5242
5243        editor.handle_input("{", cx);
5244        editor.handle_input("{", cx);
5245        editor.handle_input("_", cx);
5246        assert_eq!(
5247            editor.text(cx),
5248            "
5249                a{{_}}
5250                b{{_}}
5251                c{{_}}
5252            "
5253            .unindent()
5254        );
5255        assert_eq!(
5256            editor.selections.ranges::<Point>(cx),
5257            [
5258                Point::new(0, 4)..Point::new(0, 4),
5259                Point::new(1, 4)..Point::new(1, 4),
5260                Point::new(2, 4)..Point::new(2, 4)
5261            ]
5262        );
5263
5264        editor.backspace(&Default::default(), cx);
5265        editor.backspace(&Default::default(), cx);
5266        assert_eq!(
5267            editor.text(cx),
5268            "
5269                a{}
5270                b{}
5271                c{}
5272            "
5273            .unindent()
5274        );
5275        assert_eq!(
5276            editor.selections.ranges::<Point>(cx),
5277            [
5278                Point::new(0, 2)..Point::new(0, 2),
5279                Point::new(1, 2)..Point::new(1, 2),
5280                Point::new(2, 2)..Point::new(2, 2)
5281            ]
5282        );
5283
5284        editor.delete_to_previous_word_start(&Default::default(), cx);
5285        assert_eq!(
5286            editor.text(cx),
5287            "
5288                a
5289                b
5290                c
5291            "
5292            .unindent()
5293        );
5294        assert_eq!(
5295            editor.selections.ranges::<Point>(cx),
5296            [
5297                Point::new(0, 1)..Point::new(0, 1),
5298                Point::new(1, 1)..Point::new(1, 1),
5299                Point::new(2, 1)..Point::new(2, 1)
5300            ]
5301        );
5302    });
5303}
5304
5305#[gpui::test]
5306async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
5307    init_test(cx, |settings| {
5308        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5309    });
5310
5311    let mut cx = EditorTestContext::new(cx).await;
5312
5313    let language = Arc::new(Language::new(
5314        LanguageConfig {
5315            brackets: BracketPairConfig {
5316                pairs: vec![
5317                    BracketPair {
5318                        start: "{".to_string(),
5319                        end: "}".to_string(),
5320                        close: true,
5321                        newline: true,
5322                    },
5323                    BracketPair {
5324                        start: "(".to_string(),
5325                        end: ")".to_string(),
5326                        close: true,
5327                        newline: true,
5328                    },
5329                    BracketPair {
5330                        start: "[".to_string(),
5331                        end: "]".to_string(),
5332                        close: false,
5333                        newline: true,
5334                    },
5335                ],
5336                ..Default::default()
5337            },
5338            autoclose_before: "})]".to_string(),
5339            ..Default::default()
5340        },
5341        Some(tree_sitter_rust::language()),
5342    ));
5343
5344    cx.language_registry().add(language.clone());
5345    cx.update_buffer(|buffer, cx| {
5346        buffer.set_language(Some(language), cx);
5347    });
5348
5349    cx.set_state(
5350        &"
5351            {(ˇ)}
5352            [[ˇ]]
5353            {(ˇ)}
5354        "
5355        .unindent(),
5356    );
5357
5358    cx.update_editor(|view, cx| {
5359        view.backspace(&Default::default(), cx);
5360        view.backspace(&Default::default(), cx);
5361    });
5362
5363    cx.assert_editor_state(
5364        &"
5365            ˇ
5366            ˇ]]
5367            ˇ
5368        "
5369        .unindent(),
5370    );
5371
5372    cx.update_editor(|view, cx| {
5373        view.handle_input("{", cx);
5374        view.handle_input("{", cx);
5375        view.move_right(&MoveRight, cx);
5376        view.move_right(&MoveRight, cx);
5377        view.move_left(&MoveLeft, cx);
5378        view.move_left(&MoveLeft, cx);
5379        view.backspace(&Default::default(), cx);
5380    });
5381
5382    cx.assert_editor_state(
5383        &"
5384            {ˇ}
5385            {ˇ}]]
5386            {ˇ}
5387        "
5388        .unindent(),
5389    );
5390
5391    cx.update_editor(|view, cx| {
5392        view.backspace(&Default::default(), cx);
5393    });
5394
5395    cx.assert_editor_state(
5396        &"
5397            ˇ
5398            ˇ]]
5399            ˇ
5400        "
5401        .unindent(),
5402    );
5403}
5404
5405#[gpui::test]
5406async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
5407    init_test(cx, |_| {});
5408
5409    let language = Arc::new(Language::new(
5410        LanguageConfig::default(),
5411        Some(tree_sitter_rust::language()),
5412    ));
5413
5414    let buffer = cx.new_model(|cx| {
5415        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "")
5416            .with_language(language, cx)
5417    });
5418    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5419    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5420    editor
5421        .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5422        .await;
5423
5424    _ = editor.update(cx, |editor, cx| {
5425        editor.set_auto_replace_emoji_shortcode(true);
5426
5427        editor.handle_input("Hello ", cx);
5428        editor.handle_input(":wave", cx);
5429        assert_eq!(editor.text(cx), "Hello :wave".unindent());
5430
5431        editor.handle_input(":", cx);
5432        assert_eq!(editor.text(cx), "Hello 👋".unindent());
5433
5434        editor.handle_input(" :smile", cx);
5435        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
5436
5437        editor.handle_input(":", cx);
5438        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
5439
5440        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
5441        editor.handle_input(":wave", cx);
5442        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
5443
5444        editor.handle_input(":", cx);
5445        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
5446
5447        editor.handle_input(":1", cx);
5448        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
5449
5450        editor.handle_input(":", cx);
5451        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
5452
5453        // Ensure shortcode does not get replaced when it is part of a word
5454        editor.handle_input(" Test:wave", cx);
5455        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
5456
5457        editor.handle_input(":", cx);
5458        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
5459
5460        editor.set_auto_replace_emoji_shortcode(false);
5461
5462        // Ensure shortcode does not get replaced when auto replace is off
5463        editor.handle_input(" :wave", cx);
5464        assert_eq!(
5465            editor.text(cx),
5466            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
5467        );
5468
5469        editor.handle_input(":", cx);
5470        assert_eq!(
5471            editor.text(cx),
5472            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
5473        );
5474    });
5475}
5476
5477#[gpui::test]
5478async fn test_snippets(cx: &mut gpui::TestAppContext) {
5479    init_test(cx, |_| {});
5480
5481    let (text, insertion_ranges) = marked_text_ranges(
5482        indoc! {"
5483            a.ˇ b
5484            a.ˇ b
5485            a.ˇ b
5486        "},
5487        false,
5488    );
5489
5490    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
5491    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5492
5493    _ = editor.update(cx, |editor, cx| {
5494        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
5495
5496        editor
5497            .insert_snippet(&insertion_ranges, snippet, cx)
5498            .unwrap();
5499
5500        fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
5501            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
5502            assert_eq!(editor.text(cx), expected_text);
5503            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
5504        }
5505
5506        assert(
5507            editor,
5508            cx,
5509            indoc! {"
5510                a.f(«one», two, «three») b
5511                a.f(«one», two, «three») b
5512                a.f(«one», two, «three») b
5513            "},
5514        );
5515
5516        // Can't move earlier than the first tab stop
5517        assert!(!editor.move_to_prev_snippet_tabstop(cx));
5518        assert(
5519            editor,
5520            cx,
5521            indoc! {"
5522                a.f(«one», two, «three») b
5523                a.f(«one», two, «three») b
5524                a.f(«one», two, «three») b
5525            "},
5526        );
5527
5528        assert!(editor.move_to_next_snippet_tabstop(cx));
5529        assert(
5530            editor,
5531            cx,
5532            indoc! {"
5533                a.f(one, «two», three) b
5534                a.f(one, «two», three) b
5535                a.f(one, «two», three) b
5536            "},
5537        );
5538
5539        editor.move_to_prev_snippet_tabstop(cx);
5540        assert(
5541            editor,
5542            cx,
5543            indoc! {"
5544                a.f(«one», two, «three») b
5545                a.f(«one», two, «three») b
5546                a.f(«one», two, «three») b
5547            "},
5548        );
5549
5550        assert!(editor.move_to_next_snippet_tabstop(cx));
5551        assert(
5552            editor,
5553            cx,
5554            indoc! {"
5555                a.f(one, «two», three) b
5556                a.f(one, «two», three) b
5557                a.f(one, «two», three) b
5558            "},
5559        );
5560        assert!(editor.move_to_next_snippet_tabstop(cx));
5561        assert(
5562            editor,
5563            cx,
5564            indoc! {"
5565                a.f(one, two, three)ˇ b
5566                a.f(one, two, three)ˇ b
5567                a.f(one, two, three)ˇ b
5568            "},
5569        );
5570
5571        // As soon as the last tab stop is reached, snippet state is gone
5572        editor.move_to_prev_snippet_tabstop(cx);
5573        assert(
5574            editor,
5575            cx,
5576            indoc! {"
5577                a.f(one, two, three)ˇ b
5578                a.f(one, two, three)ˇ b
5579                a.f(one, two, three)ˇ b
5580            "},
5581        );
5582    });
5583}
5584
5585#[gpui::test]
5586async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
5587    init_test(cx, |_| {});
5588
5589    let fs = FakeFs::new(cx.executor());
5590    fs.insert_file("/file.rs", Default::default()).await;
5591
5592    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5593
5594    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
5595    language_registry.add(rust_lang());
5596    let mut fake_servers = language_registry.register_fake_lsp_adapter(
5597        "Rust",
5598        FakeLspAdapter {
5599            capabilities: lsp::ServerCapabilities {
5600                document_formatting_provider: Some(lsp::OneOf::Left(true)),
5601                ..Default::default()
5602            },
5603            ..Default::default()
5604        },
5605    );
5606
5607    let buffer = project
5608        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5609        .await
5610        .unwrap();
5611
5612    cx.executor().start_waiting();
5613    let fake_server = fake_servers.next().await.unwrap();
5614
5615    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5616    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5617    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5618    assert!(cx.read(|cx| editor.is_dirty(cx)));
5619
5620    let save = editor
5621        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5622        .unwrap();
5623    fake_server
5624        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5625            assert_eq!(
5626                params.text_document.uri,
5627                lsp::Url::from_file_path("/file.rs").unwrap()
5628            );
5629            assert_eq!(params.options.tab_size, 4);
5630            Ok(Some(vec![lsp::TextEdit::new(
5631                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5632                ", ".to_string(),
5633            )]))
5634        })
5635        .next()
5636        .await;
5637    cx.executor().start_waiting();
5638    save.await;
5639
5640    assert_eq!(
5641        editor.update(cx, |editor, cx| editor.text(cx)),
5642        "one, two\nthree\n"
5643    );
5644    assert!(!cx.read(|cx| editor.is_dirty(cx)));
5645
5646    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5647    assert!(cx.read(|cx| editor.is_dirty(cx)));
5648
5649    // Ensure we can still save even if formatting hangs.
5650    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5651        assert_eq!(
5652            params.text_document.uri,
5653            lsp::Url::from_file_path("/file.rs").unwrap()
5654        );
5655        futures::future::pending::<()>().await;
5656        unreachable!()
5657    });
5658    let save = editor
5659        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5660        .unwrap();
5661    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5662    cx.executor().start_waiting();
5663    save.await;
5664    assert_eq!(
5665        editor.update(cx, |editor, cx| editor.text(cx)),
5666        "one\ntwo\nthree\n"
5667    );
5668    assert!(!cx.read(|cx| editor.is_dirty(cx)));
5669
5670    // For non-dirty buffer, no formatting request should be sent
5671    let save = editor
5672        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5673        .unwrap();
5674    let _pending_format_request = fake_server
5675        .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
5676            panic!("Should not be invoked on non-dirty buffer");
5677        })
5678        .next();
5679    cx.executor().start_waiting();
5680    save.await;
5681
5682    // Set rust language override and assert overridden tabsize is sent to language server
5683    update_test_language_settings(cx, |settings| {
5684        settings.languages.insert(
5685            "Rust".into(),
5686            LanguageSettingsContent {
5687                tab_size: NonZeroU32::new(8),
5688                ..Default::default()
5689            },
5690        );
5691    });
5692
5693    editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
5694    assert!(cx.read(|cx| editor.is_dirty(cx)));
5695    let save = editor
5696        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5697        .unwrap();
5698    fake_server
5699        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5700            assert_eq!(
5701                params.text_document.uri,
5702                lsp::Url::from_file_path("/file.rs").unwrap()
5703            );
5704            assert_eq!(params.options.tab_size, 8);
5705            Ok(Some(vec![]))
5706        })
5707        .next()
5708        .await;
5709    cx.executor().start_waiting();
5710    save.await;
5711}
5712
5713#[gpui::test]
5714async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
5715    init_test(cx, |_| {});
5716
5717    let cols = 4;
5718    let rows = 10;
5719    let sample_text_1 = sample_text(rows, cols, 'a');
5720    assert_eq!(
5721        sample_text_1,
5722        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
5723    );
5724    let sample_text_2 = sample_text(rows, cols, 'l');
5725    assert_eq!(
5726        sample_text_2,
5727        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
5728    );
5729    let sample_text_3 = sample_text(rows, cols, 'v');
5730    assert_eq!(
5731        sample_text_3,
5732        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
5733    );
5734
5735    let fs = FakeFs::new(cx.executor());
5736    fs.insert_tree(
5737        "/a",
5738        json!({
5739            "main.rs": sample_text_1,
5740            "other.rs": sample_text_2,
5741            "lib.rs": sample_text_3,
5742        }),
5743    )
5744    .await;
5745
5746    let project = Project::test(fs, ["/a".as_ref()], cx).await;
5747    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
5748    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
5749
5750    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
5751    language_registry.add(rust_lang());
5752    let mut fake_servers = language_registry.register_fake_lsp_adapter(
5753        "Rust",
5754        FakeLspAdapter {
5755            capabilities: lsp::ServerCapabilities {
5756                document_formatting_provider: Some(lsp::OneOf::Left(true)),
5757                ..Default::default()
5758            },
5759            ..Default::default()
5760        },
5761    );
5762
5763    let worktree = project.update(cx, |project, _| {
5764        let mut worktrees = project.worktrees().collect::<Vec<_>>();
5765        assert_eq!(worktrees.len(), 1);
5766        worktrees.pop().unwrap()
5767    });
5768    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
5769
5770    let buffer_1 = project
5771        .update(cx, |project, cx| {
5772            project.open_buffer((worktree_id, "main.rs"), cx)
5773        })
5774        .await
5775        .unwrap();
5776    let buffer_2 = project
5777        .update(cx, |project, cx| {
5778            project.open_buffer((worktree_id, "other.rs"), cx)
5779        })
5780        .await
5781        .unwrap();
5782    let buffer_3 = project
5783        .update(cx, |project, cx| {
5784            project.open_buffer((worktree_id, "lib.rs"), cx)
5785        })
5786        .await
5787        .unwrap();
5788
5789    let multi_buffer = cx.new_model(|cx| {
5790        let mut multi_buffer = MultiBuffer::new(0, ReadWrite);
5791        multi_buffer.push_excerpts(
5792            buffer_1.clone(),
5793            [
5794                ExcerptRange {
5795                    context: Point::new(0, 0)..Point::new(3, 0),
5796                    primary: None,
5797                },
5798                ExcerptRange {
5799                    context: Point::new(5, 0)..Point::new(7, 0),
5800                    primary: None,
5801                },
5802                ExcerptRange {
5803                    context: Point::new(9, 0)..Point::new(10, 4),
5804                    primary: None,
5805                },
5806            ],
5807            cx,
5808        );
5809        multi_buffer.push_excerpts(
5810            buffer_2.clone(),
5811            [
5812                ExcerptRange {
5813                    context: Point::new(0, 0)..Point::new(3, 0),
5814                    primary: None,
5815                },
5816                ExcerptRange {
5817                    context: Point::new(5, 0)..Point::new(7, 0),
5818                    primary: None,
5819                },
5820                ExcerptRange {
5821                    context: Point::new(9, 0)..Point::new(10, 4),
5822                    primary: None,
5823                },
5824            ],
5825            cx,
5826        );
5827        multi_buffer.push_excerpts(
5828            buffer_3.clone(),
5829            [
5830                ExcerptRange {
5831                    context: Point::new(0, 0)..Point::new(3, 0),
5832                    primary: None,
5833                },
5834                ExcerptRange {
5835                    context: Point::new(5, 0)..Point::new(7, 0),
5836                    primary: None,
5837                },
5838                ExcerptRange {
5839                    context: Point::new(9, 0)..Point::new(10, 4),
5840                    primary: None,
5841                },
5842            ],
5843            cx,
5844        );
5845        multi_buffer
5846    });
5847    let multi_buffer_editor =
5848        cx.new_view(|cx| Editor::new(EditorMode::Full, multi_buffer, Some(project.clone()), cx));
5849
5850    multi_buffer_editor.update(cx, |editor, cx| {
5851        editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
5852        editor.insert("|one|two|three|", cx);
5853    });
5854    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
5855    multi_buffer_editor.update(cx, |editor, cx| {
5856        editor.change_selections(Some(Autoscroll::Next), cx, |s| {
5857            s.select_ranges(Some(60..70))
5858        });
5859        editor.insert("|four|five|six|", cx);
5860    });
5861    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
5862
5863    // First two buffers should be edited, but not the third one.
5864    assert_eq!(
5865        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
5866        "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
5867    );
5868    buffer_1.update(cx, |buffer, _| {
5869        assert!(buffer.is_dirty());
5870        assert_eq!(
5871            buffer.text(),
5872            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
5873        )
5874    });
5875    buffer_2.update(cx, |buffer, _| {
5876        assert!(buffer.is_dirty());
5877        assert_eq!(
5878            buffer.text(),
5879            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
5880        )
5881    });
5882    buffer_3.update(cx, |buffer, _| {
5883        assert!(!buffer.is_dirty());
5884        assert_eq!(buffer.text(), sample_text_3,)
5885    });
5886
5887    cx.executor().start_waiting();
5888    let save = multi_buffer_editor
5889        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5890        .unwrap();
5891
5892    let fake_server = fake_servers.next().await.unwrap();
5893    fake_server
5894        .server
5895        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5896            Ok(Some(vec![lsp::TextEdit::new(
5897                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5898                format!("[{} formatted]", params.text_document.uri),
5899            )]))
5900        })
5901        .detach();
5902    save.await;
5903
5904    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
5905    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
5906    assert_eq!(
5907        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
5908        "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
5909    );
5910    buffer_1.update(cx, |buffer, _| {
5911        assert!(!buffer.is_dirty());
5912        assert_eq!(
5913            buffer.text(),
5914            "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
5915        )
5916    });
5917    buffer_2.update(cx, |buffer, _| {
5918        assert!(!buffer.is_dirty());
5919        assert_eq!(
5920            buffer.text(),
5921            "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
5922        )
5923    });
5924    buffer_3.update(cx, |buffer, _| {
5925        assert!(!buffer.is_dirty());
5926        assert_eq!(buffer.text(), sample_text_3,)
5927    });
5928}
5929
5930#[gpui::test]
5931async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
5932    init_test(cx, |_| {});
5933
5934    let fs = FakeFs::new(cx.executor());
5935    fs.insert_file("/file.rs", Default::default()).await;
5936
5937    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5938
5939    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
5940    language_registry.add(rust_lang());
5941    let mut fake_servers = language_registry.register_fake_lsp_adapter(
5942        "Rust",
5943        FakeLspAdapter {
5944            capabilities: lsp::ServerCapabilities {
5945                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
5946                ..Default::default()
5947            },
5948            ..Default::default()
5949        },
5950    );
5951
5952    let buffer = project
5953        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5954        .await
5955        .unwrap();
5956
5957    cx.executor().start_waiting();
5958    let fake_server = fake_servers.next().await.unwrap();
5959
5960    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5961    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5962    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5963    assert!(cx.read(|cx| editor.is_dirty(cx)));
5964
5965    let save = editor
5966        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5967        .unwrap();
5968    fake_server
5969        .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
5970            assert_eq!(
5971                params.text_document.uri,
5972                lsp::Url::from_file_path("/file.rs").unwrap()
5973            );
5974            assert_eq!(params.options.tab_size, 4);
5975            Ok(Some(vec![lsp::TextEdit::new(
5976                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5977                ", ".to_string(),
5978            )]))
5979        })
5980        .next()
5981        .await;
5982    cx.executor().start_waiting();
5983    save.await;
5984    assert_eq!(
5985        editor.update(cx, |editor, cx| editor.text(cx)),
5986        "one, two\nthree\n"
5987    );
5988    assert!(!cx.read(|cx| editor.is_dirty(cx)));
5989
5990    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5991    assert!(cx.read(|cx| editor.is_dirty(cx)));
5992
5993    // Ensure we can still save even if formatting hangs.
5994    fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
5995        move |params, _| async move {
5996            assert_eq!(
5997                params.text_document.uri,
5998                lsp::Url::from_file_path("/file.rs").unwrap()
5999            );
6000            futures::future::pending::<()>().await;
6001            unreachable!()
6002        },
6003    );
6004    let save = editor
6005        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6006        .unwrap();
6007    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6008    cx.executor().start_waiting();
6009    save.await;
6010    assert_eq!(
6011        editor.update(cx, |editor, cx| editor.text(cx)),
6012        "one\ntwo\nthree\n"
6013    );
6014    assert!(!cx.read(|cx| editor.is_dirty(cx)));
6015
6016    // For non-dirty buffer, no formatting request should be sent
6017    let save = editor
6018        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6019        .unwrap();
6020    let _pending_format_request = fake_server
6021        .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6022            panic!("Should not be invoked on non-dirty buffer");
6023        })
6024        .next();
6025    cx.executor().start_waiting();
6026    save.await;
6027
6028    // Set Rust language override and assert overridden tabsize is sent to language server
6029    update_test_language_settings(cx, |settings| {
6030        settings.languages.insert(
6031            "Rust".into(),
6032            LanguageSettingsContent {
6033                tab_size: NonZeroU32::new(8),
6034                ..Default::default()
6035            },
6036        );
6037    });
6038
6039    editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6040    assert!(cx.read(|cx| editor.is_dirty(cx)));
6041    let save = editor
6042        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6043        .unwrap();
6044    fake_server
6045        .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6046            assert_eq!(
6047                params.text_document.uri,
6048                lsp::Url::from_file_path("/file.rs").unwrap()
6049            );
6050            assert_eq!(params.options.tab_size, 8);
6051            Ok(Some(vec![]))
6052        })
6053        .next()
6054        .await;
6055    cx.executor().start_waiting();
6056    save.await;
6057}
6058
6059#[gpui::test]
6060async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
6061    init_test(cx, |settings| {
6062        settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer)
6063    });
6064
6065    let fs = FakeFs::new(cx.executor());
6066    fs.insert_file("/file.rs", Default::default()).await;
6067
6068    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6069
6070    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6071    language_registry.add(Arc::new(Language::new(
6072        LanguageConfig {
6073            name: "Rust".into(),
6074            matcher: LanguageMatcher {
6075                path_suffixes: vec!["rs".to_string()],
6076                ..Default::default()
6077            },
6078            // Enable Prettier formatting for the same buffer, and ensure
6079            // LSP is called instead of Prettier.
6080            prettier_parser_name: Some("test_parser".to_string()),
6081            ..Default::default()
6082        },
6083        Some(tree_sitter_rust::language()),
6084    )));
6085    let mut fake_servers = language_registry.register_fake_lsp_adapter(
6086        "Rust",
6087        FakeLspAdapter {
6088            capabilities: lsp::ServerCapabilities {
6089                document_formatting_provider: Some(lsp::OneOf::Left(true)),
6090                ..Default::default()
6091            },
6092            ..Default::default()
6093        },
6094    );
6095
6096    let buffer = project
6097        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6098        .await
6099        .unwrap();
6100
6101    cx.executor().start_waiting();
6102    let fake_server = fake_servers.next().await.unwrap();
6103
6104    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6105    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6106    _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6107
6108    let format = editor
6109        .update(cx, |editor, cx| {
6110            editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
6111        })
6112        .unwrap();
6113    fake_server
6114        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6115            assert_eq!(
6116                params.text_document.uri,
6117                lsp::Url::from_file_path("/file.rs").unwrap()
6118            );
6119            assert_eq!(params.options.tab_size, 4);
6120            Ok(Some(vec![lsp::TextEdit::new(
6121                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6122                ", ".to_string(),
6123            )]))
6124        })
6125        .next()
6126        .await;
6127    cx.executor().start_waiting();
6128    format.await;
6129    assert_eq!(
6130        editor.update(cx, |editor, cx| editor.text(cx)),
6131        "one, two\nthree\n"
6132    );
6133
6134    _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6135    // Ensure we don't lock if formatting hangs.
6136    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6137        assert_eq!(
6138            params.text_document.uri,
6139            lsp::Url::from_file_path("/file.rs").unwrap()
6140        );
6141        futures::future::pending::<()>().await;
6142        unreachable!()
6143    });
6144    let format = editor
6145        .update(cx, |editor, cx| {
6146            editor.perform_format(project, FormatTrigger::Manual, cx)
6147        })
6148        .unwrap();
6149    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6150    cx.executor().start_waiting();
6151    format.await;
6152    assert_eq!(
6153        editor.update(cx, |editor, cx| editor.text(cx)),
6154        "one\ntwo\nthree\n"
6155    );
6156}
6157
6158#[gpui::test]
6159async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
6160    init_test(cx, |_| {});
6161
6162    let mut cx = EditorLspTestContext::new_rust(
6163        lsp::ServerCapabilities {
6164            document_formatting_provider: Some(lsp::OneOf::Left(true)),
6165            ..Default::default()
6166        },
6167        cx,
6168    )
6169    .await;
6170
6171    cx.set_state(indoc! {"
6172        one.twoˇ
6173    "});
6174
6175    // The format request takes a long time. When it completes, it inserts
6176    // a newline and an indent before the `.`
6177    cx.lsp
6178        .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
6179            let executor = cx.background_executor().clone();
6180            async move {
6181                executor.timer(Duration::from_millis(100)).await;
6182                Ok(Some(vec![lsp::TextEdit {
6183                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
6184                    new_text: "\n    ".into(),
6185                }]))
6186            }
6187        });
6188
6189    // Submit a format request.
6190    let format_1 = cx
6191        .update_editor(|editor, cx| editor.format(&Format, cx))
6192        .unwrap();
6193    cx.executor().run_until_parked();
6194
6195    // Submit a second format request.
6196    let format_2 = cx
6197        .update_editor(|editor, cx| editor.format(&Format, cx))
6198        .unwrap();
6199    cx.executor().run_until_parked();
6200
6201    // Wait for both format requests to complete
6202    cx.executor().advance_clock(Duration::from_millis(200));
6203    cx.executor().start_waiting();
6204    format_1.await.unwrap();
6205    cx.executor().start_waiting();
6206    format_2.await.unwrap();
6207
6208    // The formatting edits only happens once.
6209    cx.assert_editor_state(indoc! {"
6210        one
6211            .twoˇ
6212    "});
6213}
6214
6215#[gpui::test]
6216async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
6217    init_test(cx, |settings| {
6218        settings.defaults.formatter = Some(language_settings::Formatter::Auto)
6219    });
6220
6221    let mut cx = EditorLspTestContext::new_rust(
6222        lsp::ServerCapabilities {
6223            document_formatting_provider: Some(lsp::OneOf::Left(true)),
6224            ..Default::default()
6225        },
6226        cx,
6227    )
6228    .await;
6229
6230    // Set up a buffer white some trailing whitespace and no trailing newline.
6231    cx.set_state(
6232        &[
6233            "one ",   //
6234            "twoˇ",   //
6235            "three ", //
6236            "four",   //
6237        ]
6238        .join("\n"),
6239    );
6240
6241    // Submit a format request.
6242    let format = cx
6243        .update_editor(|editor, cx| editor.format(&Format, cx))
6244        .unwrap();
6245
6246    // Record which buffer changes have been sent to the language server
6247    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
6248    cx.lsp
6249        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
6250            let buffer_changes = buffer_changes.clone();
6251            move |params, _| {
6252                buffer_changes.lock().extend(
6253                    params
6254                        .content_changes
6255                        .into_iter()
6256                        .map(|e| (e.range.unwrap(), e.text)),
6257                );
6258            }
6259        });
6260
6261    // Handle formatting requests to the language server.
6262    cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
6263        let buffer_changes = buffer_changes.clone();
6264        move |_, _| {
6265            // When formatting is requested, trailing whitespace has already been stripped,
6266            // and the trailing newline has already been added.
6267            assert_eq!(
6268                &buffer_changes.lock()[1..],
6269                &[
6270                    (
6271                        lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
6272                        "".into()
6273                    ),
6274                    (
6275                        lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
6276                        "".into()
6277                    ),
6278                    (
6279                        lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
6280                        "\n".into()
6281                    ),
6282                ]
6283            );
6284
6285            // Insert blank lines between each line of the buffer.
6286            async move {
6287                Ok(Some(vec![
6288                    lsp::TextEdit {
6289                        range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
6290                        new_text: "\n".into(),
6291                    },
6292                    lsp::TextEdit {
6293                        range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
6294                        new_text: "\n".into(),
6295                    },
6296                ]))
6297            }
6298        }
6299    });
6300
6301    // After formatting the buffer, the trailing whitespace is stripped,
6302    // a newline is appended, and the edits provided by the language server
6303    // have been applied.
6304    format.await.unwrap();
6305    cx.assert_editor_state(
6306        &[
6307            "one",   //
6308            "",      //
6309            "twoˇ",  //
6310            "",      //
6311            "three", //
6312            "four",  //
6313            "",      //
6314        ]
6315        .join("\n"),
6316    );
6317
6318    // Undoing the formatting undoes the trailing whitespace removal, the
6319    // trailing newline, and the LSP edits.
6320    cx.update_buffer(|buffer, cx| buffer.undo(cx));
6321    cx.assert_editor_state(
6322        &[
6323            "one ",   //
6324            "twoˇ",   //
6325            "three ", //
6326            "four",   //
6327        ]
6328        .join("\n"),
6329    );
6330}
6331
6332#[gpui::test]
6333async fn test_completion(cx: &mut gpui::TestAppContext) {
6334    init_test(cx, |_| {});
6335
6336    let mut cx = EditorLspTestContext::new_rust(
6337        lsp::ServerCapabilities {
6338            completion_provider: Some(lsp::CompletionOptions {
6339                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
6340                resolve_provider: Some(true),
6341                ..Default::default()
6342            }),
6343            ..Default::default()
6344        },
6345        cx,
6346    )
6347    .await;
6348
6349    cx.set_state(indoc! {"
6350        oneˇ
6351        two
6352        three
6353    "});
6354    cx.simulate_keystroke(".");
6355    handle_completion_request(
6356        &mut cx,
6357        indoc! {"
6358            one.|<>
6359            two
6360            three
6361        "},
6362        vec!["first_completion", "second_completion"],
6363    )
6364    .await;
6365    cx.condition(|editor, _| editor.context_menu_visible())
6366        .await;
6367    let apply_additional_edits = cx.update_editor(|editor, cx| {
6368        editor.context_menu_next(&Default::default(), cx);
6369        editor
6370            .confirm_completion(&ConfirmCompletion::default(), cx)
6371            .unwrap()
6372    });
6373    cx.assert_editor_state(indoc! {"
6374        one.second_completionˇ
6375        two
6376        three
6377    "});
6378
6379    handle_resolve_completion_request(
6380        &mut cx,
6381        Some(vec![
6382            (
6383                //This overlaps with the primary completion edit which is
6384                //misbehavior from the LSP spec, test that we filter it out
6385                indoc! {"
6386                    one.second_ˇcompletion
6387                    two
6388                    threeˇ
6389                "},
6390                "overlapping additional edit",
6391            ),
6392            (
6393                indoc! {"
6394                    one.second_completion
6395                    two
6396                    threeˇ
6397                "},
6398                "\nadditional edit",
6399            ),
6400        ]),
6401    )
6402    .await;
6403    apply_additional_edits.await.unwrap();
6404    cx.assert_editor_state(indoc! {"
6405        one.second_completionˇ
6406        two
6407        three
6408        additional edit
6409    "});
6410
6411    cx.set_state(indoc! {"
6412        one.second_completion
6413        twoˇ
6414        threeˇ
6415        additional edit
6416    "});
6417    cx.simulate_keystroke(" ");
6418    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6419    cx.simulate_keystroke("s");
6420    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6421
6422    cx.assert_editor_state(indoc! {"
6423        one.second_completion
6424        two sˇ
6425        three sˇ
6426        additional edit
6427    "});
6428    handle_completion_request(
6429        &mut cx,
6430        indoc! {"
6431            one.second_completion
6432            two s
6433            three <s|>
6434            additional edit
6435        "},
6436        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
6437    )
6438    .await;
6439    cx.condition(|editor, _| editor.context_menu_visible())
6440        .await;
6441
6442    cx.simulate_keystroke("i");
6443
6444    handle_completion_request(
6445        &mut cx,
6446        indoc! {"
6447            one.second_completion
6448            two si
6449            three <si|>
6450            additional edit
6451        "},
6452        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
6453    )
6454    .await;
6455    cx.condition(|editor, _| editor.context_menu_visible())
6456        .await;
6457
6458    let apply_additional_edits = cx.update_editor(|editor, cx| {
6459        editor
6460            .confirm_completion(&ConfirmCompletion::default(), cx)
6461            .unwrap()
6462    });
6463    cx.assert_editor_state(indoc! {"
6464        one.second_completion
6465        two sixth_completionˇ
6466        three sixth_completionˇ
6467        additional edit
6468    "});
6469
6470    handle_resolve_completion_request(&mut cx, None).await;
6471    apply_additional_edits.await.unwrap();
6472
6473    _ = cx.update(|cx| {
6474        cx.update_global::<SettingsStore, _>(|settings, cx| {
6475            settings.update_user_settings::<EditorSettings>(cx, |settings| {
6476                settings.show_completions_on_input = Some(false);
6477            });
6478        })
6479    });
6480    cx.set_state("editorˇ");
6481    cx.simulate_keystroke(".");
6482    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6483    cx.simulate_keystroke("c");
6484    cx.simulate_keystroke("l");
6485    cx.simulate_keystroke("o");
6486    cx.assert_editor_state("editor.cloˇ");
6487    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6488    cx.update_editor(|editor, cx| {
6489        editor.show_completions(&ShowCompletions, cx);
6490    });
6491    handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
6492    cx.condition(|editor, _| editor.context_menu_visible())
6493        .await;
6494    let apply_additional_edits = cx.update_editor(|editor, cx| {
6495        editor
6496            .confirm_completion(&ConfirmCompletion::default(), cx)
6497            .unwrap()
6498    });
6499    cx.assert_editor_state("editor.closeˇ");
6500    handle_resolve_completion_request(&mut cx, None).await;
6501    apply_additional_edits.await.unwrap();
6502}
6503
6504#[gpui::test]
6505async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
6506    init_test(cx, |_| {});
6507    let mut cx = EditorTestContext::new(cx).await;
6508    let language = Arc::new(Language::new(
6509        LanguageConfig {
6510            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
6511            ..Default::default()
6512        },
6513        Some(tree_sitter_rust::language()),
6514    ));
6515    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
6516
6517    // If multiple selections intersect a line, the line is only toggled once.
6518    cx.set_state(indoc! {"
6519        fn a() {
6520            «//b();
6521            ˇ»// «c();
6522            //ˇ»  d();
6523        }
6524    "});
6525
6526    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6527
6528    cx.assert_editor_state(indoc! {"
6529        fn a() {
6530            «b();
6531            c();
6532            ˇ» d();
6533        }
6534    "});
6535
6536    // The comment prefix is inserted at the same column for every line in a
6537    // selection.
6538    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6539
6540    cx.assert_editor_state(indoc! {"
6541        fn a() {
6542            // «b();
6543            // c();
6544            ˇ»//  d();
6545        }
6546    "});
6547
6548    // If a selection ends at the beginning of a line, that line is not toggled.
6549    cx.set_selections_state(indoc! {"
6550        fn a() {
6551            // b();
6552            «// c();
6553        ˇ»    //  d();
6554        }
6555    "});
6556
6557    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6558
6559    cx.assert_editor_state(indoc! {"
6560        fn a() {
6561            // b();
6562            «c();
6563        ˇ»    //  d();
6564        }
6565    "});
6566
6567    // If a selection span a single line and is empty, the line is toggled.
6568    cx.set_state(indoc! {"
6569        fn a() {
6570            a();
6571            b();
6572        ˇ
6573        }
6574    "});
6575
6576    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6577
6578    cx.assert_editor_state(indoc! {"
6579        fn a() {
6580            a();
6581            b();
6582        //•ˇ
6583        }
6584    "});
6585
6586    // If a selection span multiple lines, empty lines are not toggled.
6587    cx.set_state(indoc! {"
6588        fn a() {
6589            «a();
6590
6591            c();ˇ»
6592        }
6593    "});
6594
6595    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6596
6597    cx.assert_editor_state(indoc! {"
6598        fn a() {
6599            // «a();
6600
6601            // c();ˇ»
6602        }
6603    "});
6604
6605    // If a selection includes multiple comment prefixes, all lines are uncommented.
6606    cx.set_state(indoc! {"
6607        fn a() {
6608            «// a();
6609            /// b();
6610            //! c();ˇ»
6611        }
6612    "});
6613
6614    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6615
6616    cx.assert_editor_state(indoc! {"
6617        fn a() {
6618            «a();
6619            b();
6620            c();ˇ»
6621        }
6622    "});
6623}
6624
6625#[gpui::test]
6626async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
6627    init_test(cx, |_| {});
6628
6629    let language = Arc::new(Language::new(
6630        LanguageConfig {
6631            line_comments: vec!["// ".into()],
6632            ..Default::default()
6633        },
6634        Some(tree_sitter_rust::language()),
6635    ));
6636
6637    let mut cx = EditorTestContext::new(cx).await;
6638
6639    cx.language_registry().add(language.clone());
6640    cx.update_buffer(|buffer, cx| {
6641        buffer.set_language(Some(language), cx);
6642    });
6643
6644    let toggle_comments = &ToggleComments {
6645        advance_downwards: true,
6646    };
6647
6648    // Single cursor on one line -> advance
6649    // Cursor moves horizontally 3 characters as well on non-blank line
6650    cx.set_state(indoc!(
6651        "fn a() {
6652             ˇdog();
6653             cat();
6654        }"
6655    ));
6656    cx.update_editor(|editor, cx| {
6657        editor.toggle_comments(toggle_comments, cx);
6658    });
6659    cx.assert_editor_state(indoc!(
6660        "fn a() {
6661             // dog();
6662             catˇ();
6663        }"
6664    ));
6665
6666    // Single selection on one line -> don't advance
6667    cx.set_state(indoc!(
6668        "fn a() {
6669             «dog()ˇ»;
6670             cat();
6671        }"
6672    ));
6673    cx.update_editor(|editor, cx| {
6674        editor.toggle_comments(toggle_comments, cx);
6675    });
6676    cx.assert_editor_state(indoc!(
6677        "fn a() {
6678             // «dog()ˇ»;
6679             cat();
6680        }"
6681    ));
6682
6683    // Multiple cursors on one line -> advance
6684    cx.set_state(indoc!(
6685        "fn a() {
6686             ˇdˇog();
6687             cat();
6688        }"
6689    ));
6690    cx.update_editor(|editor, cx| {
6691        editor.toggle_comments(toggle_comments, cx);
6692    });
6693    cx.assert_editor_state(indoc!(
6694        "fn a() {
6695             // dog();
6696             catˇ(ˇ);
6697        }"
6698    ));
6699
6700    // Multiple cursors on one line, with selection -> don't advance
6701    cx.set_state(indoc!(
6702        "fn a() {
6703             ˇdˇog«()ˇ»;
6704             cat();
6705        }"
6706    ));
6707    cx.update_editor(|editor, cx| {
6708        editor.toggle_comments(toggle_comments, cx);
6709    });
6710    cx.assert_editor_state(indoc!(
6711        "fn a() {
6712             // ˇdˇog«()ˇ»;
6713             cat();
6714        }"
6715    ));
6716
6717    // Single cursor on one line -> advance
6718    // Cursor moves to column 0 on blank line
6719    cx.set_state(indoc!(
6720        "fn a() {
6721             ˇdog();
6722
6723             cat();
6724        }"
6725    ));
6726    cx.update_editor(|editor, cx| {
6727        editor.toggle_comments(toggle_comments, cx);
6728    });
6729    cx.assert_editor_state(indoc!(
6730        "fn a() {
6731             // dog();
6732        ˇ
6733             cat();
6734        }"
6735    ));
6736
6737    // Single cursor on one line -> advance
6738    // Cursor starts and ends at column 0
6739    cx.set_state(indoc!(
6740        "fn a() {
6741         ˇ    dog();
6742             cat();
6743        }"
6744    ));
6745    cx.update_editor(|editor, cx| {
6746        editor.toggle_comments(toggle_comments, cx);
6747    });
6748    cx.assert_editor_state(indoc!(
6749        "fn a() {
6750             // dog();
6751         ˇ    cat();
6752        }"
6753    ));
6754}
6755
6756#[gpui::test]
6757async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
6758    init_test(cx, |_| {});
6759
6760    let mut cx = EditorTestContext::new(cx).await;
6761
6762    let html_language = Arc::new(
6763        Language::new(
6764            LanguageConfig {
6765                name: "HTML".into(),
6766                block_comment: Some(("<!-- ".into(), " -->".into())),
6767                ..Default::default()
6768            },
6769            Some(tree_sitter_html::language()),
6770        )
6771        .with_injection_query(
6772            r#"
6773            (script_element
6774                (raw_text) @content
6775                (#set! "language" "javascript"))
6776            "#,
6777        )
6778        .unwrap(),
6779    );
6780
6781    let javascript_language = Arc::new(Language::new(
6782        LanguageConfig {
6783            name: "JavaScript".into(),
6784            line_comments: vec!["// ".into()],
6785            ..Default::default()
6786        },
6787        Some(tree_sitter_typescript::language_tsx()),
6788    ));
6789
6790    cx.language_registry().add(html_language.clone());
6791    cx.language_registry().add(javascript_language.clone());
6792    cx.update_buffer(|buffer, cx| {
6793        buffer.set_language(Some(html_language), cx);
6794    });
6795
6796    // Toggle comments for empty selections
6797    cx.set_state(
6798        &r#"
6799            <p>A</p>ˇ
6800            <p>B</p>ˇ
6801            <p>C</p>ˇ
6802        "#
6803        .unindent(),
6804    );
6805    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6806    cx.assert_editor_state(
6807        &r#"
6808            <!-- <p>A</p>ˇ -->
6809            <!-- <p>B</p>ˇ -->
6810            <!-- <p>C</p>ˇ -->
6811        "#
6812        .unindent(),
6813    );
6814    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6815    cx.assert_editor_state(
6816        &r#"
6817            <p>A</p>ˇ
6818            <p>B</p>ˇ
6819            <p>C</p>ˇ
6820        "#
6821        .unindent(),
6822    );
6823
6824    // Toggle comments for mixture of empty and non-empty selections, where
6825    // multiple selections occupy a given line.
6826    cx.set_state(
6827        &r#"
6828            <p>A«</p>
6829            <p>ˇ»B</p>ˇ
6830            <p>C«</p>
6831            <p>ˇ»D</p>ˇ
6832        "#
6833        .unindent(),
6834    );
6835
6836    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6837    cx.assert_editor_state(
6838        &r#"
6839            <!-- <p>A«</p>
6840            <p>ˇ»B</p>ˇ -->
6841            <!-- <p>C«</p>
6842            <p>ˇ»D</p>ˇ -->
6843        "#
6844        .unindent(),
6845    );
6846    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6847    cx.assert_editor_state(
6848        &r#"
6849            <p>A«</p>
6850            <p>ˇ»B</p>ˇ
6851            <p>C«</p>
6852            <p>ˇ»D</p>ˇ
6853        "#
6854        .unindent(),
6855    );
6856
6857    // Toggle comments when different languages are active for different
6858    // selections.
6859    cx.set_state(
6860        &r#"
6861            ˇ<script>
6862                ˇvar x = new Y();
6863            ˇ</script>
6864        "#
6865        .unindent(),
6866    );
6867    cx.executor().run_until_parked();
6868    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6869    cx.assert_editor_state(
6870        &r#"
6871            <!-- ˇ<script> -->
6872                // ˇvar x = new Y();
6873            <!-- ˇ</script> -->
6874        "#
6875        .unindent(),
6876    );
6877}
6878
6879#[gpui::test]
6880fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
6881    init_test(cx, |_| {});
6882
6883    let buffer = cx.new_model(|cx| {
6884        Buffer::new(
6885            0,
6886            BufferId::new(cx.entity_id().as_u64()).unwrap(),
6887            sample_text(3, 4, 'a'),
6888        )
6889    });
6890    let multibuffer = cx.new_model(|cx| {
6891        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
6892        multibuffer.push_excerpts(
6893            buffer.clone(),
6894            [
6895                ExcerptRange {
6896                    context: Point::new(0, 0)..Point::new(0, 4),
6897                    primary: None,
6898                },
6899                ExcerptRange {
6900                    context: Point::new(1, 0)..Point::new(1, 4),
6901                    primary: None,
6902                },
6903            ],
6904            cx,
6905        );
6906        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
6907        multibuffer
6908    });
6909
6910    let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
6911    _ = view.update(cx, |view, cx| {
6912        assert_eq!(view.text(cx), "aaaa\nbbbb");
6913        view.change_selections(None, cx, |s| {
6914            s.select_ranges([
6915                Point::new(0, 0)..Point::new(0, 0),
6916                Point::new(1, 0)..Point::new(1, 0),
6917            ])
6918        });
6919
6920        view.handle_input("X", cx);
6921        assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
6922        assert_eq!(
6923            view.selections.ranges(cx),
6924            [
6925                Point::new(0, 1)..Point::new(0, 1),
6926                Point::new(1, 1)..Point::new(1, 1),
6927            ]
6928        );
6929
6930        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
6931        view.change_selections(None, cx, |s| {
6932            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
6933        });
6934        view.backspace(&Default::default(), cx);
6935        assert_eq!(view.text(cx), "Xa\nbbb");
6936        assert_eq!(
6937            view.selections.ranges(cx),
6938            [Point::new(1, 0)..Point::new(1, 0)]
6939        );
6940
6941        view.change_selections(None, cx, |s| {
6942            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
6943        });
6944        view.backspace(&Default::default(), cx);
6945        assert_eq!(view.text(cx), "X\nbb");
6946        assert_eq!(
6947            view.selections.ranges(cx),
6948            [Point::new(0, 1)..Point::new(0, 1)]
6949        );
6950    });
6951}
6952
6953#[gpui::test]
6954fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
6955    init_test(cx, |_| {});
6956
6957    let markers = vec![('[', ']').into(), ('(', ')').into()];
6958    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
6959        indoc! {"
6960            [aaaa
6961            (bbbb]
6962            cccc)",
6963        },
6964        markers.clone(),
6965    );
6966    let excerpt_ranges = markers.into_iter().map(|marker| {
6967        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
6968        ExcerptRange {
6969            context,
6970            primary: None,
6971        }
6972    });
6973    let buffer = cx.new_model(|cx| {
6974        Buffer::new(
6975            0,
6976            BufferId::new(cx.entity_id().as_u64()).unwrap(),
6977            initial_text,
6978        )
6979    });
6980    let multibuffer = cx.new_model(|cx| {
6981        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
6982        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
6983        multibuffer
6984    });
6985
6986    let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
6987    _ = view.update(cx, |view, cx| {
6988        let (expected_text, selection_ranges) = marked_text_ranges(
6989            indoc! {"
6990                aaaa
6991                bˇbbb
6992                bˇbbˇb
6993                cccc"
6994            },
6995            true,
6996        );
6997        assert_eq!(view.text(cx), expected_text);
6998        view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
6999
7000        view.handle_input("X", cx);
7001
7002        let (expected_text, expected_selections) = marked_text_ranges(
7003            indoc! {"
7004                aaaa
7005                bXˇbbXb
7006                bXˇbbXˇb
7007                cccc"
7008            },
7009            false,
7010        );
7011        assert_eq!(view.text(cx), expected_text);
7012        assert_eq!(view.selections.ranges(cx), expected_selections);
7013
7014        view.newline(&Newline, cx);
7015        let (expected_text, expected_selections) = marked_text_ranges(
7016            indoc! {"
7017                aaaa
7018                bX
7019                ˇbbX
7020                b
7021                bX
7022                ˇbbX
7023                ˇb
7024                cccc"
7025            },
7026            false,
7027        );
7028        assert_eq!(view.text(cx), expected_text);
7029        assert_eq!(view.selections.ranges(cx), expected_selections);
7030    });
7031}
7032
7033#[gpui::test]
7034fn test_refresh_selections(cx: &mut TestAppContext) {
7035    init_test(cx, |_| {});
7036
7037    let buffer = cx.new_model(|cx| {
7038        Buffer::new(
7039            0,
7040            BufferId::new(cx.entity_id().as_u64()).unwrap(),
7041            sample_text(3, 4, 'a'),
7042        )
7043    });
7044    let mut excerpt1_id = None;
7045    let multibuffer = cx.new_model(|cx| {
7046        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7047        excerpt1_id = multibuffer
7048            .push_excerpts(
7049                buffer.clone(),
7050                [
7051                    ExcerptRange {
7052                        context: Point::new(0, 0)..Point::new(1, 4),
7053                        primary: None,
7054                    },
7055                    ExcerptRange {
7056                        context: Point::new(1, 0)..Point::new(2, 4),
7057                        primary: None,
7058                    },
7059                ],
7060                cx,
7061            )
7062            .into_iter()
7063            .next();
7064        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
7065        multibuffer
7066    });
7067
7068    let editor = cx.add_window(|cx| {
7069        let mut editor = build_editor(multibuffer.clone(), cx);
7070        let snapshot = editor.snapshot(cx);
7071        editor.change_selections(None, cx, |s| {
7072            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
7073        });
7074        editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
7075        assert_eq!(
7076            editor.selections.ranges(cx),
7077            [
7078                Point::new(1, 3)..Point::new(1, 3),
7079                Point::new(2, 1)..Point::new(2, 1),
7080            ]
7081        );
7082        editor
7083    });
7084
7085    // Refreshing selections is a no-op when excerpts haven't changed.
7086    _ = editor.update(cx, |editor, cx| {
7087        editor.change_selections(None, cx, |s| s.refresh());
7088        assert_eq!(
7089            editor.selections.ranges(cx),
7090            [
7091                Point::new(1, 3)..Point::new(1, 3),
7092                Point::new(2, 1)..Point::new(2, 1),
7093            ]
7094        );
7095    });
7096
7097    _ = multibuffer.update(cx, |multibuffer, cx| {
7098        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
7099    });
7100    _ = editor.update(cx, |editor, cx| {
7101        // Removing an excerpt causes the first selection to become degenerate.
7102        assert_eq!(
7103            editor.selections.ranges(cx),
7104            [
7105                Point::new(0, 0)..Point::new(0, 0),
7106                Point::new(0, 1)..Point::new(0, 1)
7107            ]
7108        );
7109
7110        // Refreshing selections will relocate the first selection to the original buffer
7111        // location.
7112        editor.change_selections(None, cx, |s| s.refresh());
7113        assert_eq!(
7114            editor.selections.ranges(cx),
7115            [
7116                Point::new(0, 1)..Point::new(0, 1),
7117                Point::new(0, 3)..Point::new(0, 3)
7118            ]
7119        );
7120        assert!(editor.selections.pending_anchor().is_some());
7121    });
7122}
7123
7124#[gpui::test]
7125fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
7126    init_test(cx, |_| {});
7127
7128    let buffer = cx.new_model(|cx| {
7129        Buffer::new(
7130            0,
7131            BufferId::new(cx.entity_id().as_u64()).unwrap(),
7132            sample_text(3, 4, 'a'),
7133        )
7134    });
7135    let mut excerpt1_id = None;
7136    let multibuffer = cx.new_model(|cx| {
7137        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7138        excerpt1_id = multibuffer
7139            .push_excerpts(
7140                buffer.clone(),
7141                [
7142                    ExcerptRange {
7143                        context: Point::new(0, 0)..Point::new(1, 4),
7144                        primary: None,
7145                    },
7146                    ExcerptRange {
7147                        context: Point::new(1, 0)..Point::new(2, 4),
7148                        primary: None,
7149                    },
7150                ],
7151                cx,
7152            )
7153            .into_iter()
7154            .next();
7155        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
7156        multibuffer
7157    });
7158
7159    let editor = cx.add_window(|cx| {
7160        let mut editor = build_editor(multibuffer.clone(), cx);
7161        let snapshot = editor.snapshot(cx);
7162        editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
7163        assert_eq!(
7164            editor.selections.ranges(cx),
7165            [Point::new(1, 3)..Point::new(1, 3)]
7166        );
7167        editor
7168    });
7169
7170    _ = multibuffer.update(cx, |multibuffer, cx| {
7171        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
7172    });
7173    _ = editor.update(cx, |editor, cx| {
7174        assert_eq!(
7175            editor.selections.ranges(cx),
7176            [Point::new(0, 0)..Point::new(0, 0)]
7177        );
7178
7179        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
7180        editor.change_selections(None, cx, |s| s.refresh());
7181        assert_eq!(
7182            editor.selections.ranges(cx),
7183            [Point::new(0, 3)..Point::new(0, 3)]
7184        );
7185        assert!(editor.selections.pending_anchor().is_some());
7186    });
7187}
7188
7189#[gpui::test]
7190async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
7191    init_test(cx, |_| {});
7192
7193    let language = Arc::new(
7194        Language::new(
7195            LanguageConfig {
7196                brackets: BracketPairConfig {
7197                    pairs: vec![
7198                        BracketPair {
7199                            start: "{".to_string(),
7200                            end: "}".to_string(),
7201                            close: true,
7202                            newline: true,
7203                        },
7204                        BracketPair {
7205                            start: "/* ".to_string(),
7206                            end: " */".to_string(),
7207                            close: true,
7208                            newline: true,
7209                        },
7210                    ],
7211                    ..Default::default()
7212                },
7213                ..Default::default()
7214            },
7215            Some(tree_sitter_rust::language()),
7216        )
7217        .with_indents_query("")
7218        .unwrap(),
7219    );
7220
7221    let text = concat!(
7222        "{   }\n",     //
7223        "  x\n",       //
7224        "  /*   */\n", //
7225        "x\n",         //
7226        "{{} }\n",     //
7227    );
7228
7229    let buffer = cx.new_model(|cx| {
7230        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
7231            .with_language(language, cx)
7232    });
7233    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7234    let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7235    view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
7236        .await;
7237
7238    _ = view.update(cx, |view, cx| {
7239        view.change_selections(None, cx, |s| {
7240            s.select_display_ranges([
7241                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
7242                DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
7243                DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
7244            ])
7245        });
7246        view.newline(&Newline, cx);
7247
7248        assert_eq!(
7249            view.buffer().read(cx).read(cx).text(),
7250            concat!(
7251                "{ \n",    // Suppress rustfmt
7252                "\n",      //
7253                "}\n",     //
7254                "  x\n",   //
7255                "  /* \n", //
7256                "  \n",    //
7257                "  */\n",  //
7258                "x\n",     //
7259                "{{} \n",  //
7260                "}\n",     //
7261            )
7262        );
7263    });
7264}
7265
7266#[gpui::test]
7267fn test_highlighted_ranges(cx: &mut TestAppContext) {
7268    init_test(cx, |_| {});
7269
7270    let editor = cx.add_window(|cx| {
7271        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
7272        build_editor(buffer.clone(), cx)
7273    });
7274
7275    _ = editor.update(cx, |editor, cx| {
7276        struct Type1;
7277        struct Type2;
7278
7279        let buffer = editor.buffer.read(cx).snapshot(cx);
7280
7281        let anchor_range =
7282            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
7283
7284        editor.highlight_background::<Type1>(
7285            &[
7286                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
7287                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
7288                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
7289                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
7290            ],
7291            |_| Hsla::red(),
7292            cx,
7293        );
7294        editor.highlight_background::<Type2>(
7295            &[
7296                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
7297                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
7298                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
7299                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
7300            ],
7301            |_| Hsla::green(),
7302            cx,
7303        );
7304
7305        let snapshot = editor.snapshot(cx);
7306        let mut highlighted_ranges = editor.background_highlights_in_range(
7307            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
7308            &snapshot,
7309            cx.theme().colors(),
7310        );
7311        // Enforce a consistent ordering based on color without relying on the ordering of the
7312        // highlight's `TypeId` which is non-executor.
7313        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
7314        assert_eq!(
7315            highlighted_ranges,
7316            &[
7317                (
7318                    DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
7319                    Hsla::red(),
7320                ),
7321                (
7322                    DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
7323                    Hsla::red(),
7324                ),
7325                (
7326                    DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
7327                    Hsla::green(),
7328                ),
7329                (
7330                    DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
7331                    Hsla::green(),
7332                ),
7333            ]
7334        );
7335        assert_eq!(
7336            editor.background_highlights_in_range(
7337                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
7338                &snapshot,
7339                cx.theme().colors(),
7340            ),
7341            &[(
7342                DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
7343                Hsla::red(),
7344            )]
7345        );
7346    });
7347}
7348
7349#[gpui::test]
7350async fn test_following(cx: &mut gpui::TestAppContext) {
7351    init_test(cx, |_| {});
7352
7353    let fs = FakeFs::new(cx.executor());
7354    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7355
7356    let buffer = project.update(cx, |project, cx| {
7357        let buffer = project
7358            .create_buffer(&sample_text(16, 8, 'a'), None, cx)
7359            .unwrap();
7360        cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
7361    });
7362    let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
7363    let follower = cx.update(|cx| {
7364        cx.open_window(
7365            WindowOptions {
7366                bounds: Some(Bounds::from_corners(
7367                    gpui::Point::new(0.into(), 0.into()),
7368                    gpui::Point::new(10.into(), 80.into()),
7369                )),
7370                ..Default::default()
7371            },
7372            |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
7373        )
7374    });
7375
7376    let is_still_following = Rc::new(RefCell::new(true));
7377    let follower_edit_event_count = Rc::new(RefCell::new(0));
7378    let pending_update = Rc::new(RefCell::new(None));
7379    _ = follower.update(cx, {
7380        let update = pending_update.clone();
7381        let is_still_following = is_still_following.clone();
7382        let follower_edit_event_count = follower_edit_event_count.clone();
7383        |_, cx| {
7384            cx.subscribe(
7385                &leader.root_view(cx).unwrap(),
7386                move |_, leader, event, cx| {
7387                    leader
7388                        .read(cx)
7389                        .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
7390                },
7391            )
7392            .detach();
7393
7394            cx.subscribe(
7395                &follower.root_view(cx).unwrap(),
7396                move |_, _, event: &EditorEvent, _cx| {
7397                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
7398                        *is_still_following.borrow_mut() = false;
7399                    }
7400
7401                    if let EditorEvent::BufferEdited = event {
7402                        *follower_edit_event_count.borrow_mut() += 1;
7403                    }
7404                },
7405            )
7406            .detach();
7407        }
7408    });
7409
7410    // Update the selections only
7411    _ = leader.update(cx, |leader, cx| {
7412        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
7413    });
7414    follower
7415        .update(cx, |follower, cx| {
7416            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7417        })
7418        .unwrap()
7419        .await
7420        .unwrap();
7421    _ = follower.update(cx, |follower, cx| {
7422        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
7423    });
7424    assert_eq!(*is_still_following.borrow(), true);
7425    assert_eq!(*follower_edit_event_count.borrow(), 0);
7426
7427    // Update the scroll position only
7428    _ = leader.update(cx, |leader, cx| {
7429        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
7430    });
7431    follower
7432        .update(cx, |follower, cx| {
7433            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7434        })
7435        .unwrap()
7436        .await
7437        .unwrap();
7438    assert_eq!(
7439        follower
7440            .update(cx, |follower, cx| follower.scroll_position(cx))
7441            .unwrap(),
7442        gpui::Point::new(1.5, 3.5)
7443    );
7444    assert_eq!(*is_still_following.borrow(), true);
7445    assert_eq!(*follower_edit_event_count.borrow(), 0);
7446
7447    // Update the selections and scroll position. The follower's scroll position is updated
7448    // via autoscroll, not via the leader's exact scroll position.
7449    _ = leader.update(cx, |leader, cx| {
7450        leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
7451        leader.request_autoscroll(Autoscroll::newest(), cx);
7452        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
7453    });
7454    follower
7455        .update(cx, |follower, cx| {
7456            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7457        })
7458        .unwrap()
7459        .await
7460        .unwrap();
7461    _ = follower.update(cx, |follower, cx| {
7462        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
7463        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
7464    });
7465    assert_eq!(*is_still_following.borrow(), true);
7466
7467    // Creating a pending selection that precedes another selection
7468    _ = leader.update(cx, |leader, cx| {
7469        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
7470        leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
7471    });
7472    follower
7473        .update(cx, |follower, cx| {
7474            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7475        })
7476        .unwrap()
7477        .await
7478        .unwrap();
7479    _ = follower.update(cx, |follower, cx| {
7480        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
7481    });
7482    assert_eq!(*is_still_following.borrow(), true);
7483
7484    // Extend the pending selection so that it surrounds another selection
7485    _ = leader.update(cx, |leader, cx| {
7486        leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
7487    });
7488    follower
7489        .update(cx, |follower, cx| {
7490            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7491        })
7492        .unwrap()
7493        .await
7494        .unwrap();
7495    _ = follower.update(cx, |follower, cx| {
7496        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
7497    });
7498
7499    // Scrolling locally breaks the follow
7500    _ = follower.update(cx, |follower, cx| {
7501        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
7502        follower.set_scroll_anchor(
7503            ScrollAnchor {
7504                anchor: top_anchor,
7505                offset: gpui::Point::new(0.0, 0.5),
7506            },
7507            cx,
7508        );
7509    });
7510    assert_eq!(*is_still_following.borrow(), false);
7511}
7512
7513#[gpui::test]
7514async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
7515    init_test(cx, |_| {});
7516
7517    let fs = FakeFs::new(cx.executor());
7518    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7519    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7520    let pane = workspace
7521        .update(cx, |workspace, _| workspace.active_pane().clone())
7522        .unwrap();
7523
7524    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7525
7526    let leader = pane.update(cx, |_, cx| {
7527        let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
7528        cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
7529    });
7530
7531    // Start following the editor when it has no excerpts.
7532    let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
7533    let follower_1 = cx
7534        .update_window(*workspace.deref(), |_, cx| {
7535            Editor::from_state_proto(
7536                pane.clone(),
7537                workspace.root_view(cx).unwrap(),
7538                ViewId {
7539                    creator: Default::default(),
7540                    id: 0,
7541                },
7542                &mut state_message,
7543                cx,
7544            )
7545        })
7546        .unwrap()
7547        .unwrap()
7548        .await
7549        .unwrap();
7550
7551    let update_message = Rc::new(RefCell::new(None));
7552    follower_1.update(cx, {
7553        let update = update_message.clone();
7554        |_, cx| {
7555            cx.subscribe(&leader, move |_, leader, event, cx| {
7556                leader
7557                    .read(cx)
7558                    .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
7559            })
7560            .detach();
7561        }
7562    });
7563
7564    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
7565        (
7566            project
7567                .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
7568                .unwrap(),
7569            project
7570                .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
7571                .unwrap(),
7572        )
7573    });
7574
7575    // Insert some excerpts.
7576    _ = leader.update(cx, |leader, cx| {
7577        leader.buffer.update(cx, |multibuffer, cx| {
7578            let excerpt_ids = multibuffer.push_excerpts(
7579                buffer_1.clone(),
7580                [
7581                    ExcerptRange {
7582                        context: 1..6,
7583                        primary: None,
7584                    },
7585                    ExcerptRange {
7586                        context: 12..15,
7587                        primary: None,
7588                    },
7589                    ExcerptRange {
7590                        context: 0..3,
7591                        primary: None,
7592                    },
7593                ],
7594                cx,
7595            );
7596            multibuffer.insert_excerpts_after(
7597                excerpt_ids[0],
7598                buffer_2.clone(),
7599                [
7600                    ExcerptRange {
7601                        context: 8..12,
7602                        primary: None,
7603                    },
7604                    ExcerptRange {
7605                        context: 0..6,
7606                        primary: None,
7607                    },
7608                ],
7609                cx,
7610            );
7611        });
7612    });
7613
7614    // Apply the update of adding the excerpts.
7615    follower_1
7616        .update(cx, |follower, cx| {
7617            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
7618        })
7619        .await
7620        .unwrap();
7621    assert_eq!(
7622        follower_1.update(cx, |editor, cx| editor.text(cx)),
7623        leader.update(cx, |editor, cx| editor.text(cx))
7624    );
7625    update_message.borrow_mut().take();
7626
7627    // Start following separately after it already has excerpts.
7628    let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
7629    let follower_2 = cx
7630        .update_window(*workspace.deref(), |_, cx| {
7631            Editor::from_state_proto(
7632                pane.clone(),
7633                workspace.root_view(cx).unwrap().clone(),
7634                ViewId {
7635                    creator: Default::default(),
7636                    id: 0,
7637                },
7638                &mut state_message,
7639                cx,
7640            )
7641        })
7642        .unwrap()
7643        .unwrap()
7644        .await
7645        .unwrap();
7646    assert_eq!(
7647        follower_2.update(cx, |editor, cx| editor.text(cx)),
7648        leader.update(cx, |editor, cx| editor.text(cx))
7649    );
7650
7651    // Remove some excerpts.
7652    _ = leader.update(cx, |leader, cx| {
7653        leader.buffer.update(cx, |multibuffer, cx| {
7654            let excerpt_ids = multibuffer.excerpt_ids();
7655            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
7656            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
7657        });
7658    });
7659
7660    // Apply the update of removing the excerpts.
7661    follower_1
7662        .update(cx, |follower, cx| {
7663            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
7664        })
7665        .await
7666        .unwrap();
7667    follower_2
7668        .update(cx, |follower, cx| {
7669            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
7670        })
7671        .await
7672        .unwrap();
7673    update_message.borrow_mut().take();
7674    assert_eq!(
7675        follower_1.update(cx, |editor, cx| editor.text(cx)),
7676        leader.update(cx, |editor, cx| editor.text(cx))
7677    );
7678}
7679
7680#[gpui::test]
7681async fn go_to_prev_overlapping_diagnostic(
7682    executor: BackgroundExecutor,
7683    cx: &mut gpui::TestAppContext,
7684) {
7685    init_test(cx, |_| {});
7686
7687    let mut cx = EditorTestContext::new(cx).await;
7688    let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
7689
7690    cx.set_state(indoc! {"
7691        ˇfn func(abc def: i32) -> u32 {
7692        }
7693    "});
7694
7695    _ = cx.update(|cx| {
7696        _ = project.update(cx, |project, cx| {
7697            project
7698                .update_diagnostics(
7699                    LanguageServerId(0),
7700                    lsp::PublishDiagnosticsParams {
7701                        uri: lsp::Url::from_file_path("/root/file").unwrap(),
7702                        version: None,
7703                        diagnostics: vec![
7704                            lsp::Diagnostic {
7705                                range: lsp::Range::new(
7706                                    lsp::Position::new(0, 11),
7707                                    lsp::Position::new(0, 12),
7708                                ),
7709                                severity: Some(lsp::DiagnosticSeverity::ERROR),
7710                                ..Default::default()
7711                            },
7712                            lsp::Diagnostic {
7713                                range: lsp::Range::new(
7714                                    lsp::Position::new(0, 12),
7715                                    lsp::Position::new(0, 15),
7716                                ),
7717                                severity: Some(lsp::DiagnosticSeverity::ERROR),
7718                                ..Default::default()
7719                            },
7720                            lsp::Diagnostic {
7721                                range: lsp::Range::new(
7722                                    lsp::Position::new(0, 25),
7723                                    lsp::Position::new(0, 28),
7724                                ),
7725                                severity: Some(lsp::DiagnosticSeverity::ERROR),
7726                                ..Default::default()
7727                            },
7728                        ],
7729                    },
7730                    &[],
7731                    cx,
7732                )
7733                .unwrap()
7734        });
7735    });
7736
7737    executor.run_until_parked();
7738
7739    cx.update_editor(|editor, cx| {
7740        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
7741    });
7742
7743    cx.assert_editor_state(indoc! {"
7744        fn func(abc def: i32) -> ˇu32 {
7745        }
7746    "});
7747
7748    cx.update_editor(|editor, cx| {
7749        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
7750    });
7751
7752    cx.assert_editor_state(indoc! {"
7753        fn func(abc ˇdef: i32) -> u32 {
7754        }
7755    "});
7756
7757    cx.update_editor(|editor, cx| {
7758        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
7759    });
7760
7761    cx.assert_editor_state(indoc! {"
7762        fn func(abcˇ def: i32) -> u32 {
7763        }
7764    "});
7765
7766    cx.update_editor(|editor, cx| {
7767        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
7768    });
7769
7770    cx.assert_editor_state(indoc! {"
7771        fn func(abc def: i32) -> ˇu32 {
7772        }
7773    "});
7774}
7775
7776#[gpui::test]
7777async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7778    init_test(cx, |_| {});
7779
7780    let mut cx = EditorTestContext::new(cx).await;
7781
7782    let diff_base = r#"
7783        use some::mod;
7784
7785        const A: u32 = 42;
7786
7787        fn main() {
7788            println!("hello");
7789
7790            println!("world");
7791        }
7792        "#
7793    .unindent();
7794
7795    // Edits are modified, removed, modified, added
7796    cx.set_state(
7797        &r#"
7798        use some::modified;
7799
7800        ˇ
7801        fn main() {
7802            println!("hello there");
7803
7804            println!("around the");
7805            println!("world");
7806        }
7807        "#
7808        .unindent(),
7809    );
7810
7811    cx.set_diff_base(Some(&diff_base));
7812    executor.run_until_parked();
7813
7814    cx.update_editor(|editor, cx| {
7815        //Wrap around the bottom of the buffer
7816        for _ in 0..3 {
7817            editor.go_to_hunk(&GoToHunk, cx);
7818        }
7819    });
7820
7821    cx.assert_editor_state(
7822        &r#"
7823        ˇuse some::modified;
7824
7825
7826        fn main() {
7827            println!("hello there");
7828
7829            println!("around the");
7830            println!("world");
7831        }
7832        "#
7833        .unindent(),
7834    );
7835
7836    cx.update_editor(|editor, cx| {
7837        //Wrap around the top of the buffer
7838        for _ in 0..2 {
7839            editor.go_to_prev_hunk(&GoToPrevHunk, cx);
7840        }
7841    });
7842
7843    cx.assert_editor_state(
7844        &r#"
7845        use some::modified;
7846
7847
7848        fn main() {
7849        ˇ    println!("hello there");
7850
7851            println!("around the");
7852            println!("world");
7853        }
7854        "#
7855        .unindent(),
7856    );
7857
7858    cx.update_editor(|editor, cx| {
7859        editor.go_to_prev_hunk(&GoToPrevHunk, cx);
7860    });
7861
7862    cx.assert_editor_state(
7863        &r#"
7864        use some::modified;
7865
7866        ˇ
7867        fn main() {
7868            println!("hello there");
7869
7870            println!("around the");
7871            println!("world");
7872        }
7873        "#
7874        .unindent(),
7875    );
7876
7877    cx.update_editor(|editor, cx| {
7878        for _ in 0..3 {
7879            editor.go_to_prev_hunk(&GoToPrevHunk, cx);
7880        }
7881    });
7882
7883    cx.assert_editor_state(
7884        &r#"
7885        use some::modified;
7886
7887
7888        fn main() {
7889        ˇ    println!("hello there");
7890
7891            println!("around the");
7892            println!("world");
7893        }
7894        "#
7895        .unindent(),
7896    );
7897
7898    cx.update_editor(|editor, cx| {
7899        editor.fold(&Fold, cx);
7900
7901        //Make sure that the fold only gets one hunk
7902        for _ in 0..4 {
7903            editor.go_to_hunk(&GoToHunk, cx);
7904        }
7905    });
7906
7907    cx.assert_editor_state(
7908        &r#"
7909        ˇuse some::modified;
7910
7911
7912        fn main() {
7913            println!("hello there");
7914
7915            println!("around the");
7916            println!("world");
7917        }
7918        "#
7919        .unindent(),
7920    );
7921}
7922
7923#[test]
7924fn test_split_words() {
7925    fn split(text: &str) -> Vec<&str> {
7926        split_words(text).collect()
7927    }
7928
7929    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
7930    assert_eq!(split("hello_world"), &["hello_", "world"]);
7931    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
7932    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
7933    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
7934    assert_eq!(split("helloworld"), &["helloworld"]);
7935
7936    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
7937}
7938
7939#[gpui::test]
7940async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
7941    init_test(cx, |_| {});
7942
7943    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
7944    let mut assert = |before, after| {
7945        let _state_context = cx.set_state(before);
7946        cx.update_editor(|editor, cx| {
7947            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
7948        });
7949        cx.assert_editor_state(after);
7950    };
7951
7952    // Outside bracket jumps to outside of matching bracket
7953    assert("console.logˇ(var);", "console.log(var)ˇ;");
7954    assert("console.log(var)ˇ;", "console.logˇ(var);");
7955
7956    // Inside bracket jumps to inside of matching bracket
7957    assert("console.log(ˇvar);", "console.log(varˇ);");
7958    assert("console.log(varˇ);", "console.log(ˇvar);");
7959
7960    // When outside a bracket and inside, favor jumping to the inside bracket
7961    assert(
7962        "console.log('foo', [1, 2, 3]ˇ);",
7963        "console.log(ˇ'foo', [1, 2, 3]);",
7964    );
7965    assert(
7966        "console.log(ˇ'foo', [1, 2, 3]);",
7967        "console.log('foo', [1, 2, 3]ˇ);",
7968    );
7969
7970    // Bias forward if two options are equally likely
7971    assert(
7972        "let result = curried_fun()ˇ();",
7973        "let result = curried_fun()()ˇ;",
7974    );
7975
7976    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
7977    assert(
7978        indoc! {"
7979            function test() {
7980                console.log('test')ˇ
7981            }"},
7982        indoc! {"
7983            function test() {
7984                console.logˇ('test')
7985            }"},
7986    );
7987}
7988
7989#[gpui::test]
7990async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
7991    init_test(cx, |_| {});
7992
7993    let fs = FakeFs::new(cx.executor());
7994    fs.insert_tree(
7995        "/a",
7996        json!({
7997            "main.rs": "fn main() { let a = 5; }",
7998            "other.rs": "// Test file",
7999        }),
8000    )
8001    .await;
8002    let project = Project::test(fs, ["/a".as_ref()], cx).await;
8003
8004    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8005    language_registry.add(Arc::new(Language::new(
8006        LanguageConfig {
8007            name: "Rust".into(),
8008            matcher: LanguageMatcher {
8009                path_suffixes: vec!["rs".to_string()],
8010                ..Default::default()
8011            },
8012            brackets: BracketPairConfig {
8013                pairs: vec![BracketPair {
8014                    start: "{".to_string(),
8015                    end: "}".to_string(),
8016                    close: true,
8017                    newline: true,
8018                }],
8019                disabled_scopes_by_bracket_ix: Vec::new(),
8020            },
8021            ..Default::default()
8022        },
8023        Some(tree_sitter_rust::language()),
8024    )));
8025    let mut fake_servers = language_registry.register_fake_lsp_adapter(
8026        "Rust",
8027        FakeLspAdapter {
8028            capabilities: lsp::ServerCapabilities {
8029                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
8030                    first_trigger_character: "{".to_string(),
8031                    more_trigger_character: None,
8032                }),
8033                ..Default::default()
8034            },
8035            ..Default::default()
8036        },
8037    );
8038
8039    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8040
8041    let cx = &mut VisualTestContext::from_window(*workspace, cx);
8042
8043    let worktree_id = workspace
8044        .update(cx, |workspace, cx| {
8045            workspace.project().update(cx, |project, cx| {
8046                project.worktrees().next().unwrap().read(cx).id()
8047            })
8048        })
8049        .unwrap();
8050
8051    let buffer = project
8052        .update(cx, |project, cx| {
8053            project.open_local_buffer("/a/main.rs", cx)
8054        })
8055        .await
8056        .unwrap();
8057    cx.executor().run_until_parked();
8058    cx.executor().start_waiting();
8059    let fake_server = fake_servers.next().await.unwrap();
8060    let editor_handle = workspace
8061        .update(cx, |workspace, cx| {
8062            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
8063        })
8064        .unwrap()
8065        .await
8066        .unwrap()
8067        .downcast::<Editor>()
8068        .unwrap();
8069
8070    fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
8071        assert_eq!(
8072            params.text_document_position.text_document.uri,
8073            lsp::Url::from_file_path("/a/main.rs").unwrap(),
8074        );
8075        assert_eq!(
8076            params.text_document_position.position,
8077            lsp::Position::new(0, 21),
8078        );
8079
8080        Ok(Some(vec![lsp::TextEdit {
8081            new_text: "]".to_string(),
8082            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
8083        }]))
8084    });
8085
8086    editor_handle.update(cx, |editor, cx| {
8087        editor.focus(cx);
8088        editor.change_selections(None, cx, |s| {
8089            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
8090        });
8091        editor.handle_input("{", cx);
8092    });
8093
8094    cx.executor().run_until_parked();
8095
8096    _ = buffer.update(cx, |buffer, _| {
8097        assert_eq!(
8098            buffer.text(),
8099            "fn main() { let a = {5}; }",
8100            "No extra braces from on type formatting should appear in the buffer"
8101        )
8102    });
8103}
8104
8105#[gpui::test]
8106async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
8107    init_test(cx, |_| {});
8108
8109    let fs = FakeFs::new(cx.executor());
8110    fs.insert_tree(
8111        "/a",
8112        json!({
8113            "main.rs": "fn main() { let a = 5; }",
8114            "other.rs": "// Test file",
8115        }),
8116    )
8117    .await;
8118
8119    let project = Project::test(fs, ["/a".as_ref()], cx).await;
8120
8121    let server_restarts = Arc::new(AtomicUsize::new(0));
8122    let closure_restarts = Arc::clone(&server_restarts);
8123    let language_server_name = "test language server";
8124    let language_name: Arc<str> = "Rust".into();
8125
8126    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8127    language_registry.add(Arc::new(Language::new(
8128        LanguageConfig {
8129            name: Arc::clone(&language_name),
8130            matcher: LanguageMatcher {
8131                path_suffixes: vec!["rs".to_string()],
8132                ..Default::default()
8133            },
8134            ..Default::default()
8135        },
8136        Some(tree_sitter_rust::language()),
8137    )));
8138    let mut fake_servers = language_registry.register_fake_lsp_adapter(
8139        "Rust",
8140        FakeLspAdapter {
8141            name: language_server_name,
8142            initialization_options: Some(json!({
8143                "testOptionValue": true
8144            })),
8145            initializer: Some(Box::new(move |fake_server| {
8146                let task_restarts = Arc::clone(&closure_restarts);
8147                fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
8148                    task_restarts.fetch_add(1, atomic::Ordering::Release);
8149                    futures::future::ready(Ok(()))
8150                });
8151            })),
8152            ..Default::default()
8153        },
8154    );
8155
8156    let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8157    let _buffer = project
8158        .update(cx, |project, cx| {
8159            project.open_local_buffer("/a/main.rs", cx)
8160        })
8161        .await
8162        .unwrap();
8163    let _fake_server = fake_servers.next().await.unwrap();
8164    update_test_language_settings(cx, |language_settings| {
8165        language_settings.languages.insert(
8166            Arc::clone(&language_name),
8167            LanguageSettingsContent {
8168                tab_size: NonZeroU32::new(8),
8169                ..Default::default()
8170            },
8171        );
8172    });
8173    cx.executor().run_until_parked();
8174    assert_eq!(
8175        server_restarts.load(atomic::Ordering::Acquire),
8176        0,
8177        "Should not restart LSP server on an unrelated change"
8178    );
8179
8180    update_test_project_settings(cx, |project_settings| {
8181        project_settings.lsp.insert(
8182            "Some other server name".into(),
8183            LspSettings {
8184                binary: None,
8185                settings: None,
8186                initialization_options: Some(json!({
8187                    "some other init value": false
8188                })),
8189            },
8190        );
8191    });
8192    cx.executor().run_until_parked();
8193    assert_eq!(
8194        server_restarts.load(atomic::Ordering::Acquire),
8195        0,
8196        "Should not restart LSP server on an unrelated LSP settings change"
8197    );
8198
8199    update_test_project_settings(cx, |project_settings| {
8200        project_settings.lsp.insert(
8201            language_server_name.into(),
8202            LspSettings {
8203                binary: None,
8204                settings: None,
8205                initialization_options: Some(json!({
8206                    "anotherInitValue": false
8207                })),
8208            },
8209        );
8210    });
8211    cx.executor().run_until_parked();
8212    assert_eq!(
8213        server_restarts.load(atomic::Ordering::Acquire),
8214        1,
8215        "Should restart LSP server on a related LSP settings change"
8216    );
8217
8218    update_test_project_settings(cx, |project_settings| {
8219        project_settings.lsp.insert(
8220            language_server_name.into(),
8221            LspSettings {
8222                binary: None,
8223                settings: None,
8224                initialization_options: Some(json!({
8225                    "anotherInitValue": false
8226                })),
8227            },
8228        );
8229    });
8230    cx.executor().run_until_parked();
8231    assert_eq!(
8232        server_restarts.load(atomic::Ordering::Acquire),
8233        1,
8234        "Should not restart LSP server on a related LSP settings change that is the same"
8235    );
8236
8237    update_test_project_settings(cx, |project_settings| {
8238        project_settings.lsp.insert(
8239            language_server_name.into(),
8240            LspSettings {
8241                binary: None,
8242                settings: None,
8243                initialization_options: None,
8244            },
8245        );
8246    });
8247    cx.executor().run_until_parked();
8248    assert_eq!(
8249        server_restarts.load(atomic::Ordering::Acquire),
8250        2,
8251        "Should restart LSP server on another related LSP settings change"
8252    );
8253}
8254
8255#[gpui::test]
8256async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
8257    init_test(cx, |_| {});
8258
8259    let mut cx = EditorLspTestContext::new_rust(
8260        lsp::ServerCapabilities {
8261            completion_provider: Some(lsp::CompletionOptions {
8262                trigger_characters: Some(vec![".".to_string()]),
8263                resolve_provider: Some(true),
8264                ..Default::default()
8265            }),
8266            ..Default::default()
8267        },
8268        cx,
8269    )
8270    .await;
8271
8272    cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8273    cx.simulate_keystroke(".");
8274    let completion_item = lsp::CompletionItem {
8275        label: "some".into(),
8276        kind: Some(lsp::CompletionItemKind::SNIPPET),
8277        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8278        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8279            kind: lsp::MarkupKind::Markdown,
8280            value: "```rust\nSome(2)\n```".to_string(),
8281        })),
8282        deprecated: Some(false),
8283        sort_text: Some("fffffff2".to_string()),
8284        filter_text: Some("some".to_string()),
8285        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8286        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8287            range: lsp::Range {
8288                start: lsp::Position {
8289                    line: 0,
8290                    character: 22,
8291                },
8292                end: lsp::Position {
8293                    line: 0,
8294                    character: 22,
8295                },
8296            },
8297            new_text: "Some(2)".to_string(),
8298        })),
8299        additional_text_edits: Some(vec![lsp::TextEdit {
8300            range: lsp::Range {
8301                start: lsp::Position {
8302                    line: 0,
8303                    character: 20,
8304                },
8305                end: lsp::Position {
8306                    line: 0,
8307                    character: 22,
8308                },
8309            },
8310            new_text: "".to_string(),
8311        }]),
8312        ..Default::default()
8313    };
8314
8315    let closure_completion_item = completion_item.clone();
8316    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8317        let task_completion_item = closure_completion_item.clone();
8318        async move {
8319            Ok(Some(lsp::CompletionResponse::Array(vec![
8320                task_completion_item,
8321            ])))
8322        }
8323    });
8324
8325    request.next().await;
8326
8327    cx.condition(|editor, _| editor.context_menu_visible())
8328        .await;
8329    let apply_additional_edits = cx.update_editor(|editor, cx| {
8330        editor
8331            .confirm_completion(&ConfirmCompletion::default(), cx)
8332            .unwrap()
8333    });
8334    cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
8335
8336    cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
8337        let task_completion_item = completion_item.clone();
8338        async move { Ok(task_completion_item) }
8339    })
8340    .next()
8341    .await
8342    .unwrap();
8343    apply_additional_edits.await.unwrap();
8344    cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
8345}
8346
8347#[gpui::test]
8348async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
8349    init_test(cx, |_| {});
8350
8351    let mut cx = EditorLspTestContext::new(
8352        Language::new(
8353            LanguageConfig {
8354                matcher: LanguageMatcher {
8355                    path_suffixes: vec!["jsx".into()],
8356                    ..Default::default()
8357                },
8358                overrides: [(
8359                    "element".into(),
8360                    LanguageConfigOverride {
8361                        word_characters: Override::Set(['-'].into_iter().collect()),
8362                        ..Default::default()
8363                    },
8364                )]
8365                .into_iter()
8366                .collect(),
8367                ..Default::default()
8368            },
8369            Some(tree_sitter_typescript::language_tsx()),
8370        )
8371        .with_override_query("(jsx_self_closing_element) @element")
8372        .unwrap(),
8373        lsp::ServerCapabilities {
8374            completion_provider: Some(lsp::CompletionOptions {
8375                trigger_characters: Some(vec![":".to_string()]),
8376                ..Default::default()
8377            }),
8378            ..Default::default()
8379        },
8380        cx,
8381    )
8382    .await;
8383
8384    cx.lsp
8385        .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8386            Ok(Some(lsp::CompletionResponse::Array(vec![
8387                lsp::CompletionItem {
8388                    label: "bg-blue".into(),
8389                    ..Default::default()
8390                },
8391                lsp::CompletionItem {
8392                    label: "bg-red".into(),
8393                    ..Default::default()
8394                },
8395                lsp::CompletionItem {
8396                    label: "bg-yellow".into(),
8397                    ..Default::default()
8398                },
8399            ])))
8400        });
8401
8402    cx.set_state(r#"<p class="bgˇ" />"#);
8403
8404    // Trigger completion when typing a dash, because the dash is an extra
8405    // word character in the 'element' scope, which contains the cursor.
8406    cx.simulate_keystroke("-");
8407    cx.executor().run_until_parked();
8408    cx.update_editor(|editor, _| {
8409        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8410            assert_eq!(
8411                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8412                &["bg-red", "bg-blue", "bg-yellow"]
8413            );
8414        } else {
8415            panic!("expected completion menu to be open");
8416        }
8417    });
8418
8419    cx.simulate_keystroke("l");
8420    cx.executor().run_until_parked();
8421    cx.update_editor(|editor, _| {
8422        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8423            assert_eq!(
8424                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8425                &["bg-blue", "bg-yellow"]
8426            );
8427        } else {
8428            panic!("expected completion menu to be open");
8429        }
8430    });
8431
8432    // When filtering completions, consider the character after the '-' to
8433    // be the start of a subword.
8434    cx.set_state(r#"<p class="yelˇ" />"#);
8435    cx.simulate_keystroke("l");
8436    cx.executor().run_until_parked();
8437    cx.update_editor(|editor, _| {
8438        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8439            assert_eq!(
8440                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8441                &["bg-yellow"]
8442            );
8443        } else {
8444            panic!("expected completion menu to be open");
8445        }
8446    });
8447}
8448
8449#[gpui::test]
8450async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
8451    init_test(cx, |settings| {
8452        settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
8453    });
8454
8455    let fs = FakeFs::new(cx.executor());
8456    fs.insert_file("/file.rs", Default::default()).await;
8457
8458    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8459    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8460
8461    language_registry.add(Arc::new(Language::new(
8462        LanguageConfig {
8463            name: "Rust".into(),
8464            matcher: LanguageMatcher {
8465                path_suffixes: vec!["rs".to_string()],
8466                ..Default::default()
8467            },
8468            prettier_parser_name: Some("test_parser".to_string()),
8469            ..Default::default()
8470        },
8471        Some(tree_sitter_rust::language()),
8472    )));
8473
8474    let test_plugin = "test_plugin";
8475    let _ = language_registry.register_fake_lsp_adapter(
8476        "Rust",
8477        FakeLspAdapter {
8478            prettier_plugins: vec![test_plugin],
8479            ..Default::default()
8480        },
8481    );
8482
8483    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
8484    let buffer = project
8485        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
8486        .await
8487        .unwrap();
8488
8489    let buffer_text = "one\ntwo\nthree\n";
8490    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
8491    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8492    _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
8493
8494    editor
8495        .update(cx, |editor, cx| {
8496            editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8497        })
8498        .unwrap()
8499        .await;
8500    assert_eq!(
8501        editor.update(cx, |editor, cx| editor.text(cx)),
8502        buffer_text.to_string() + prettier_format_suffix,
8503        "Test prettier formatting was not applied to the original buffer text",
8504    );
8505
8506    update_test_language_settings(cx, |settings| {
8507        settings.defaults.formatter = Some(language_settings::Formatter::Auto)
8508    });
8509    let format = editor.update(cx, |editor, cx| {
8510        editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8511    });
8512    format.await.unwrap();
8513    assert_eq!(
8514        editor.update(cx, |editor, cx| editor.text(cx)),
8515        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
8516        "Autoformatting (via test prettier) was not applied to the original buffer text",
8517    );
8518}
8519
8520#[gpui::test]
8521async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
8522    init_test(cx, |_| {});
8523    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
8524    let base_text = indoc! {r#"struct Row;
8525struct Row1;
8526struct Row2;
8527
8528struct Row4;
8529struct Row5;
8530struct Row6;
8531
8532struct Row8;
8533struct Row9;
8534struct Row10;"#};
8535
8536    // When addition hunks are not adjacent to carets, no hunk revert is performed
8537    assert_hunk_revert(
8538        indoc! {r#"struct Row;
8539                   struct Row1;
8540                   struct Row1.1;
8541                   struct Row1.2;
8542                   struct Row2;ˇ
8543
8544                   struct Row4;
8545                   struct Row5;
8546                   struct Row6;
8547
8548                   struct Row8;
8549                   ˇstruct Row9;
8550                   struct Row9.1;
8551                   struct Row9.2;
8552                   struct Row9.3;
8553                   struct Row10;"#},
8554        vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
8555        indoc! {r#"struct Row;
8556                   struct Row1;
8557                   struct Row1.1;
8558                   struct Row1.2;
8559                   struct Row2;ˇ
8560
8561                   struct Row4;
8562                   struct Row5;
8563                   struct Row6;
8564
8565                   struct Row8;
8566                   ˇstruct Row9;
8567                   struct Row9.1;
8568                   struct Row9.2;
8569                   struct Row9.3;
8570                   struct Row10;"#},
8571        base_text,
8572        &mut cx,
8573    );
8574    // Same for selections
8575    assert_hunk_revert(
8576        indoc! {r#"struct Row;
8577                   struct Row1;
8578                   struct Row2;
8579                   struct Row2.1;
8580                   struct Row2.2;
8581                   «ˇ
8582                   struct Row4;
8583                   struct» Row5;
8584                   «struct Row6;
8585                   ˇ»
8586                   struct Row9.1;
8587                   struct Row9.2;
8588                   struct Row9.3;
8589                   struct Row8;
8590                   struct Row9;
8591                   struct Row10;"#},
8592        vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
8593        indoc! {r#"struct Row;
8594                   struct Row1;
8595                   struct Row2;
8596                   struct Row2.1;
8597                   struct Row2.2;
8598                   «ˇ
8599                   struct Row4;
8600                   struct» Row5;
8601                   «struct Row6;
8602                   ˇ»
8603                   struct Row9.1;
8604                   struct Row9.2;
8605                   struct Row9.3;
8606                   struct Row8;
8607                   struct Row9;
8608                   struct Row10;"#},
8609        base_text,
8610        &mut cx,
8611    );
8612
8613    // When carets and selections intersect the addition hunks, those are reverted.
8614    // Adjacent carets got merged.
8615    assert_hunk_revert(
8616        indoc! {r#"struct Row;
8617                   ˇ// something on the top
8618                   struct Row1;
8619                   struct Row2;
8620                   struct Roˇw3.1;
8621                   struct Row2.2;
8622                   struct Row2.3;ˇ
8623
8624                   struct Row4;
8625                   struct ˇRow5.1;
8626                   struct Row5.2;
8627                   struct «Rowˇ»5.3;
8628                   struct Row5;
8629                   struct Row6;
8630                   ˇ
8631                   struct Row9.1;
8632                   struct «Rowˇ»9.2;
8633                   struct «ˇRow»9.3;
8634                   struct Row8;
8635                   struct Row9;
8636                   «ˇ// something on bottom»
8637                   struct Row10;"#},
8638        vec![
8639            DiffHunkStatus::Added,
8640            DiffHunkStatus::Added,
8641            DiffHunkStatus::Added,
8642            DiffHunkStatus::Added,
8643            DiffHunkStatus::Added,
8644        ],
8645        indoc! {r#"struct Row;
8646                   ˇstruct Row1;
8647                   struct Row2;
8648                   ˇ
8649                   struct Row4;
8650                   ˇstruct Row5;
8651                   struct Row6;
8652                   ˇ
8653                   ˇstruct Row8;
8654                   struct Row9;
8655                   ˇstruct Row10;"#},
8656        base_text,
8657        &mut cx,
8658    );
8659}
8660
8661#[gpui::test]
8662async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
8663    init_test(cx, |_| {});
8664    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
8665    let base_text = indoc! {r#"struct Row;
8666struct Row1;
8667struct Row2;
8668
8669struct Row4;
8670struct Row5;
8671struct Row6;
8672
8673struct Row8;
8674struct Row9;
8675struct Row10;"#};
8676
8677    // Modification hunks behave the same as the addition ones.
8678    assert_hunk_revert(
8679        indoc! {r#"struct Row;
8680                   struct Row1;
8681                   struct Row33;
8682                   ˇ
8683                   struct Row4;
8684                   struct Row5;
8685                   struct Row6;
8686                   ˇ
8687                   struct Row99;
8688                   struct Row9;
8689                   struct Row10;"#},
8690        vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
8691        indoc! {r#"struct Row;
8692                   struct Row1;
8693                   struct Row33;
8694                   ˇ
8695                   struct Row4;
8696                   struct Row5;
8697                   struct Row6;
8698                   ˇ
8699                   struct Row99;
8700                   struct Row9;
8701                   struct Row10;"#},
8702        base_text,
8703        &mut cx,
8704    );
8705    assert_hunk_revert(
8706        indoc! {r#"struct Row;
8707                   struct Row1;
8708                   struct Row33;
8709                   «ˇ
8710                   struct Row4;
8711                   struct» Row5;
8712                   «struct Row6;
8713                   ˇ»
8714                   struct Row99;
8715                   struct Row9;
8716                   struct Row10;"#},
8717        vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
8718        indoc! {r#"struct Row;
8719                   struct Row1;
8720                   struct Row33;
8721                   «ˇ
8722                   struct Row4;
8723                   struct» Row5;
8724                   «struct Row6;
8725                   ˇ»
8726                   struct Row99;
8727                   struct Row9;
8728                   struct Row10;"#},
8729        base_text,
8730        &mut cx,
8731    );
8732
8733    assert_hunk_revert(
8734        indoc! {r#"ˇstruct Row1.1;
8735                   struct Row1;
8736                   «ˇstr»uct Row22;
8737
8738                   struct ˇRow44;
8739                   struct Row5;
8740                   struct «Rˇ»ow66;ˇ
8741
8742                   «struˇ»ct Row88;
8743                   struct Row9;
8744                   struct Row1011;ˇ"#},
8745        vec![
8746            DiffHunkStatus::Modified,
8747            DiffHunkStatus::Modified,
8748            DiffHunkStatus::Modified,
8749            DiffHunkStatus::Modified,
8750            DiffHunkStatus::Modified,
8751            DiffHunkStatus::Modified,
8752        ],
8753        indoc! {r#"struct Row;
8754                   ˇstruct Row1;
8755                   struct Row2;
8756                   ˇ
8757                   struct Row4;
8758                   ˇstruct Row5;
8759                   struct Row6;
8760                   ˇ
8761                   struct Row8;
8762                   ˇstruct Row9;
8763                   struct Row10;ˇ"#},
8764        base_text,
8765        &mut cx,
8766    );
8767}
8768
8769#[gpui::test]
8770async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
8771    init_test(cx, |_| {});
8772    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
8773    let base_text = indoc! {r#"struct Row;
8774struct Row1;
8775struct Row2;
8776
8777struct Row4;
8778struct Row5;
8779struct Row6;
8780
8781struct Row8;
8782struct Row9;
8783struct Row10;"#};
8784
8785    // Deletion hunks trigger with carets on ajacent rows, so carets and selections have to stay farther to avoid the revert
8786    assert_hunk_revert(
8787        indoc! {r#"struct Row;
8788                   struct Row2;
8789
8790                   ˇstruct Row4;
8791                   struct Row5;
8792                   struct Row6;
8793                   ˇ
8794                   struct Row8;
8795                   struct Row10;"#},
8796        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
8797        indoc! {r#"struct Row;
8798                   struct Row2;
8799
8800                   ˇstruct Row4;
8801                   struct Row5;
8802                   struct Row6;
8803                   ˇ
8804                   struct Row8;
8805                   struct Row10;"#},
8806        base_text,
8807        &mut cx,
8808    );
8809    assert_hunk_revert(
8810        indoc! {r#"struct Row;
8811                   struct Row2;
8812
8813                   «ˇstruct Row4;
8814                   struct» Row5;
8815                   «struct Row6;
8816                   ˇ»
8817                   struct Row8;
8818                   struct Row10;"#},
8819        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
8820        indoc! {r#"struct Row;
8821                   struct Row2;
8822
8823                   «ˇstruct Row4;
8824                   struct» Row5;
8825                   «struct Row6;
8826                   ˇ»
8827                   struct Row8;
8828                   struct Row10;"#},
8829        base_text,
8830        &mut cx,
8831    );
8832
8833    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
8834    assert_hunk_revert(
8835        indoc! {r#"struct Row;
8836                   ˇstruct Row2;
8837
8838                   struct Row4;
8839                   struct Row5;
8840                   struct Row6;
8841
8842                   struct Row8;ˇ
8843                   struct Row10;"#},
8844        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
8845        indoc! {r#"struct Row;
8846                   struct Row1;
8847                   ˇstruct Row2;
8848
8849                   struct Row4;
8850                   struct Row5;
8851                   struct Row6;
8852
8853                   struct Row8;ˇ
8854                   struct Row9;
8855                   struct Row10;"#},
8856        base_text,
8857        &mut cx,
8858    );
8859    assert_hunk_revert(
8860        indoc! {r#"struct Row;
8861                   struct Row2«ˇ;
8862                   struct Row4;
8863                   struct» Row5;
8864                   «struct Row6;
8865
8866                   struct Row8;ˇ»
8867                   struct Row10;"#},
8868        vec![
8869            DiffHunkStatus::Removed,
8870            DiffHunkStatus::Removed,
8871            DiffHunkStatus::Removed,
8872        ],
8873        indoc! {r#"struct Row;
8874                   struct Row1;
8875                   struct Row2«ˇ;
8876
8877                   struct Row4;
8878                   struct» Row5;
8879                   «struct Row6;
8880
8881                   struct Row8;ˇ»
8882                   struct Row9;
8883                   struct Row10;"#},
8884        base_text,
8885        &mut cx,
8886    );
8887}
8888
8889#[gpui::test]
8890async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
8891    init_test(cx, |_| {});
8892
8893    let cols = 4;
8894    let rows = 10;
8895    let sample_text_1 = sample_text(rows, cols, 'a');
8896    assert_eq!(
8897        sample_text_1,
8898        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
8899    );
8900    let sample_text_2 = sample_text(rows, cols, 'l');
8901    assert_eq!(
8902        sample_text_2,
8903        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
8904    );
8905    let sample_text_3 = sample_text(rows, cols, 'v');
8906    assert_eq!(
8907        sample_text_3,
8908        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
8909    );
8910
8911    fn diff_every_buffer_row(
8912        buffer: &Model<Buffer>,
8913        sample_text: String,
8914        cols: usize,
8915        cx: &mut gpui::TestAppContext,
8916    ) {
8917        // revert first character in each row, creating one large diff hunk per buffer
8918        let is_first_char = |offset: usize| offset % cols == 0;
8919        buffer.update(cx, |buffer, cx| {
8920            buffer.set_text(
8921                sample_text
8922                    .chars()
8923                    .enumerate()
8924                    .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
8925                    .collect::<String>(),
8926                cx,
8927            );
8928            buffer.set_diff_base(Some(sample_text), cx);
8929        });
8930        cx.executor().run_until_parked();
8931    }
8932
8933    let buffer_1 = cx.new_model(|cx| {
8934        Buffer::new(
8935            0,
8936            BufferId::new(cx.entity_id().as_u64()).unwrap(),
8937            sample_text_1.clone(),
8938        )
8939    });
8940    diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
8941
8942    let buffer_2 = cx.new_model(|cx| {
8943        Buffer::new(
8944            1,
8945            BufferId::new(cx.entity_id().as_u64() + 1).unwrap(),
8946            sample_text_2.clone(),
8947        )
8948    });
8949    diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
8950
8951    let buffer_3 = cx.new_model(|cx| {
8952        Buffer::new(
8953            2,
8954            BufferId::new(cx.entity_id().as_u64() + 2).unwrap(),
8955            sample_text_3.clone(),
8956        )
8957    });
8958    diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
8959
8960    let multibuffer = cx.new_model(|cx| {
8961        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8962        multibuffer.push_excerpts(
8963            buffer_1.clone(),
8964            [
8965                ExcerptRange {
8966                    context: Point::new(0, 0)..Point::new(3, 0),
8967                    primary: None,
8968                },
8969                ExcerptRange {
8970                    context: Point::new(5, 0)..Point::new(7, 0),
8971                    primary: None,
8972                },
8973                ExcerptRange {
8974                    context: Point::new(9, 0)..Point::new(10, 4),
8975                    primary: None,
8976                },
8977            ],
8978            cx,
8979        );
8980        multibuffer.push_excerpts(
8981            buffer_2.clone(),
8982            [
8983                ExcerptRange {
8984                    context: Point::new(0, 0)..Point::new(3, 0),
8985                    primary: None,
8986                },
8987                ExcerptRange {
8988                    context: Point::new(5, 0)..Point::new(7, 0),
8989                    primary: None,
8990                },
8991                ExcerptRange {
8992                    context: Point::new(9, 0)..Point::new(10, 4),
8993                    primary: None,
8994                },
8995            ],
8996            cx,
8997        );
8998        multibuffer.push_excerpts(
8999            buffer_3.clone(),
9000            [
9001                ExcerptRange {
9002                    context: Point::new(0, 0)..Point::new(3, 0),
9003                    primary: None,
9004                },
9005                ExcerptRange {
9006                    context: Point::new(5, 0)..Point::new(7, 0),
9007                    primary: None,
9008                },
9009                ExcerptRange {
9010                    context: Point::new(9, 0)..Point::new(10, 4),
9011                    primary: None,
9012                },
9013            ],
9014            cx,
9015        );
9016        multibuffer
9017    });
9018
9019    let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9020    editor.update(cx, |editor, cx| {
9021        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");
9022        editor.select_all(&SelectAll, cx);
9023        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
9024    });
9025    cx.executor().run_until_parked();
9026    // When all ranges are selected, all buffer hunks are reverted.
9027    editor.update(cx, |editor, cx| {
9028        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");
9029    });
9030    buffer_1.update(cx, |buffer, _| {
9031        assert_eq!(buffer.text(), sample_text_1);
9032    });
9033    buffer_2.update(cx, |buffer, _| {
9034        assert_eq!(buffer.text(), sample_text_2);
9035    });
9036    buffer_3.update(cx, |buffer, _| {
9037        assert_eq!(buffer.text(), sample_text_3);
9038    });
9039
9040    diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
9041    diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
9042    diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
9043    editor.update(cx, |editor, cx| {
9044        editor.change_selections(None, cx, |s| {
9045            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
9046        });
9047        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
9048    });
9049    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
9050    // but not affect buffer_2 and its related excerpts.
9051    editor.update(cx, |editor, cx| {
9052        assert_eq!(
9053            editor.text(cx),
9054            "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"
9055        );
9056    });
9057    buffer_1.update(cx, |buffer, _| {
9058        assert_eq!(buffer.text(), sample_text_1);
9059    });
9060    buffer_2.update(cx, |buffer, _| {
9061        assert_eq!(
9062            buffer.text(),
9063            "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
9064        );
9065    });
9066    buffer_3.update(cx, |buffer, _| {
9067        assert_eq!(
9068            buffer.text(),
9069            "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
9070        );
9071    });
9072}
9073
9074#[gpui::test]
9075async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
9076    init_test(cx, |_| {});
9077
9078    let cols = 4;
9079    let rows = 10;
9080    let sample_text_1 = sample_text(rows, cols, 'a');
9081    assert_eq!(
9082        sample_text_1,
9083        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
9084    );
9085    let sample_text_2 = sample_text(rows, cols, 'l');
9086    assert_eq!(
9087        sample_text_2,
9088        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
9089    );
9090    let sample_text_3 = sample_text(rows, cols, 'v');
9091    assert_eq!(
9092        sample_text_3,
9093        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
9094    );
9095
9096    let buffer_1 = cx.new_model(|cx| {
9097        Buffer::new(
9098            0,
9099            BufferId::new(cx.entity_id().as_u64()).unwrap(),
9100            sample_text_1.clone(),
9101        )
9102    });
9103
9104    let buffer_2 = cx.new_model(|cx| {
9105        Buffer::new(
9106            1,
9107            BufferId::new(cx.entity_id().as_u64() + 1).unwrap(),
9108            sample_text_2.clone(),
9109        )
9110    });
9111
9112    let buffer_3 = cx.new_model(|cx| {
9113        Buffer::new(
9114            2,
9115            BufferId::new(cx.entity_id().as_u64() + 2).unwrap(),
9116            sample_text_3.clone(),
9117        )
9118    });
9119
9120    let multi_buffer = cx.new_model(|cx| {
9121        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
9122        multibuffer.push_excerpts(
9123            buffer_1.clone(),
9124            [
9125                ExcerptRange {
9126                    context: Point::new(0, 0)..Point::new(3, 0),
9127                    primary: None,
9128                },
9129                ExcerptRange {
9130                    context: Point::new(5, 0)..Point::new(7, 0),
9131                    primary: None,
9132                },
9133                ExcerptRange {
9134                    context: Point::new(9, 0)..Point::new(10, 4),
9135                    primary: None,
9136                },
9137            ],
9138            cx,
9139        );
9140        multibuffer.push_excerpts(
9141            buffer_2.clone(),
9142            [
9143                ExcerptRange {
9144                    context: Point::new(0, 0)..Point::new(3, 0),
9145                    primary: None,
9146                },
9147                ExcerptRange {
9148                    context: Point::new(5, 0)..Point::new(7, 0),
9149                    primary: None,
9150                },
9151                ExcerptRange {
9152                    context: Point::new(9, 0)..Point::new(10, 4),
9153                    primary: None,
9154                },
9155            ],
9156            cx,
9157        );
9158        multibuffer.push_excerpts(
9159            buffer_3.clone(),
9160            [
9161                ExcerptRange {
9162                    context: Point::new(0, 0)..Point::new(3, 0),
9163                    primary: None,
9164                },
9165                ExcerptRange {
9166                    context: Point::new(5, 0)..Point::new(7, 0),
9167                    primary: None,
9168                },
9169                ExcerptRange {
9170                    context: Point::new(9, 0)..Point::new(10, 4),
9171                    primary: None,
9172                },
9173            ],
9174            cx,
9175        );
9176        multibuffer
9177    });
9178
9179    let fs = FakeFs::new(cx.executor());
9180    fs.insert_tree(
9181        "/a",
9182        json!({
9183            "main.rs": sample_text_1,
9184            "other.rs": sample_text_2,
9185            "lib.rs": sample_text_3,
9186        }),
9187    )
9188    .await;
9189    let project = Project::test(fs, ["/a".as_ref()], cx).await;
9190    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9191    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9192    let multi_buffer_editor =
9193        cx.new_view(|cx| Editor::new(EditorMode::Full, multi_buffer, Some(project.clone()), cx));
9194    let multibuffer_item_id = workspace
9195        .update(cx, |workspace, cx| {
9196            assert!(
9197                workspace.active_item(cx).is_none(),
9198                "active item should be None before the first item is added"
9199            );
9200            workspace.add_item_to_active_pane(Box::new(multi_buffer_editor.clone()), cx);
9201            let active_item = workspace
9202                .active_item(cx)
9203                .expect("should have an active item after adding the multi buffer");
9204            assert!(
9205                !active_item.is_singleton(cx),
9206                "A multi buffer was expected to active after adding"
9207            );
9208            active_item.item_id()
9209        })
9210        .unwrap();
9211    cx.executor().run_until_parked();
9212
9213    multi_buffer_editor.update(cx, |editor, cx| {
9214        editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
9215        editor.open_excerpts(&OpenExcerpts, cx);
9216    });
9217    cx.executor().run_until_parked();
9218    let first_item_id = workspace
9219        .update(cx, |workspace, cx| {
9220            let active_item = workspace
9221                .active_item(cx)
9222                .expect("should have an active item after navigating into the 1st buffer");
9223            let first_item_id = active_item.item_id();
9224            assert_ne!(
9225                first_item_id, multibuffer_item_id,
9226                "Should navigate into the 1st buffer and activate it"
9227            );
9228            assert!(
9229                active_item.is_singleton(cx),
9230                "New active item should be a singleton buffer"
9231            );
9232            assert_eq!(
9233                active_item
9234                    .act_as::<Editor>(cx)
9235                    .expect("should have navigated into an editor for the 1st buffer")
9236                    .read(cx)
9237                    .text(cx),
9238                sample_text_1
9239            );
9240
9241            workspace
9242                .go_back(workspace.active_pane().downgrade(), cx)
9243                .detach_and_log_err(cx);
9244
9245            first_item_id
9246        })
9247        .unwrap();
9248    cx.executor().run_until_parked();
9249    workspace
9250        .update(cx, |workspace, cx| {
9251            let active_item = workspace
9252                .active_item(cx)
9253                .expect("should have an active item after navigating back");
9254            assert_eq!(
9255                active_item.item_id(),
9256                multibuffer_item_id,
9257                "Should navigate back to the multi buffer"
9258            );
9259            assert!(!active_item.is_singleton(cx));
9260        })
9261        .unwrap();
9262
9263    multi_buffer_editor.update(cx, |editor, cx| {
9264        editor.change_selections(Some(Autoscroll::Next), cx, |s| {
9265            s.select_ranges(Some(39..40))
9266        });
9267        editor.open_excerpts(&OpenExcerpts, cx);
9268    });
9269    cx.executor().run_until_parked();
9270    let second_item_id = workspace
9271        .update(cx, |workspace, cx| {
9272            let active_item = workspace
9273                .active_item(cx)
9274                .expect("should have an active item after navigating into the 2nd buffer");
9275            let second_item_id = active_item.item_id();
9276            assert_ne!(
9277                second_item_id, multibuffer_item_id,
9278                "Should navigate away from the multibuffer"
9279            );
9280            assert_ne!(
9281                second_item_id, first_item_id,
9282                "Should navigate into the 2nd buffer and activate it"
9283            );
9284            assert!(
9285                active_item.is_singleton(cx),
9286                "New active item should be a singleton buffer"
9287            );
9288            assert_eq!(
9289                active_item
9290                    .act_as::<Editor>(cx)
9291                    .expect("should have navigated into an editor")
9292                    .read(cx)
9293                    .text(cx),
9294                sample_text_2
9295            );
9296
9297            workspace
9298                .go_back(workspace.active_pane().downgrade(), cx)
9299                .detach_and_log_err(cx);
9300
9301            second_item_id
9302        })
9303        .unwrap();
9304    cx.executor().run_until_parked();
9305    workspace
9306        .update(cx, |workspace, cx| {
9307            let active_item = workspace
9308                .active_item(cx)
9309                .expect("should have an active item after navigating back from the 2nd buffer");
9310            assert_eq!(
9311                active_item.item_id(),
9312                multibuffer_item_id,
9313                "Should navigate back from the 2nd buffer to the multi buffer"
9314            );
9315            assert!(!active_item.is_singleton(cx));
9316        })
9317        .unwrap();
9318
9319    multi_buffer_editor.update(cx, |editor, cx| {
9320        editor.change_selections(Some(Autoscroll::Next), cx, |s| {
9321            s.select_ranges(Some(60..70))
9322        });
9323        editor.open_excerpts(&OpenExcerpts, cx);
9324    });
9325    cx.executor().run_until_parked();
9326    workspace
9327        .update(cx, |workspace, cx| {
9328            let active_item = workspace
9329                .active_item(cx)
9330                .expect("should have an active item after navigating into the 3rd buffer");
9331            let third_item_id = active_item.item_id();
9332            assert_ne!(
9333                third_item_id, multibuffer_item_id,
9334                "Should navigate into the 3rd buffer and activate it"
9335            );
9336            assert_ne!(third_item_id, first_item_id);
9337            assert_ne!(third_item_id, second_item_id);
9338            assert!(
9339                active_item.is_singleton(cx),
9340                "New active item should be a singleton buffer"
9341            );
9342            assert_eq!(
9343                active_item
9344                    .act_as::<Editor>(cx)
9345                    .expect("should have navigated into an editor")
9346                    .read(cx)
9347                    .text(cx),
9348                sample_text_3
9349            );
9350
9351            workspace
9352                .go_back(workspace.active_pane().downgrade(), cx)
9353                .detach_and_log_err(cx);
9354        })
9355        .unwrap();
9356    cx.executor().run_until_parked();
9357    workspace
9358        .update(cx, |workspace, cx| {
9359            let active_item = workspace
9360                .active_item(cx)
9361                .expect("should have an active item after navigating back from the 3rd buffer");
9362            assert_eq!(
9363                active_item.item_id(),
9364                multibuffer_item_id,
9365                "Should navigate back from the 3rd buffer to the multi buffer"
9366            );
9367            assert!(!active_item.is_singleton(cx));
9368        })
9369        .unwrap();
9370}
9371
9372fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
9373    let point = DisplayPoint::new(row as u32, column as u32);
9374    point..point
9375}
9376
9377fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
9378    let (text, ranges) = marked_text_ranges(marked_text, true);
9379    assert_eq!(view.text(cx), text);
9380    assert_eq!(
9381        view.selections.ranges(cx),
9382        ranges,
9383        "Assert selections are {}",
9384        marked_text
9385    );
9386}
9387
9388/// Handle completion request passing a marked string specifying where the completion
9389/// should be triggered from using '|' character, what range should be replaced, and what completions
9390/// should be returned using '<' and '>' to delimit the range
9391pub fn handle_completion_request(
9392    cx: &mut EditorLspTestContext,
9393    marked_string: &str,
9394    completions: Vec<&'static str>,
9395) -> impl Future<Output = ()> {
9396    let complete_from_marker: TextRangeMarker = '|'.into();
9397    let replace_range_marker: TextRangeMarker = ('<', '>').into();
9398    let (_, mut marked_ranges) = marked_text_ranges_by(
9399        marked_string,
9400        vec![complete_from_marker.clone(), replace_range_marker.clone()],
9401    );
9402
9403    let complete_from_position =
9404        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
9405    let replace_range =
9406        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
9407
9408    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
9409        let completions = completions.clone();
9410        async move {
9411            assert_eq!(params.text_document_position.text_document.uri, url.clone());
9412            assert_eq!(
9413                params.text_document_position.position,
9414                complete_from_position
9415            );
9416            Ok(Some(lsp::CompletionResponse::Array(
9417                completions
9418                    .iter()
9419                    .map(|completion_text| lsp::CompletionItem {
9420                        label: completion_text.to_string(),
9421                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9422                            range: replace_range,
9423                            new_text: completion_text.to_string(),
9424                        })),
9425                        ..Default::default()
9426                    })
9427                    .collect(),
9428            )))
9429        }
9430    });
9431
9432    async move {
9433        request.next().await;
9434    }
9435}
9436
9437fn handle_resolve_completion_request(
9438    cx: &mut EditorLspTestContext,
9439    edits: Option<Vec<(&'static str, &'static str)>>,
9440) -> impl Future<Output = ()> {
9441    let edits = edits.map(|edits| {
9442        edits
9443            .iter()
9444            .map(|(marked_string, new_text)| {
9445                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
9446                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
9447                lsp::TextEdit::new(replace_range, new_text.to_string())
9448            })
9449            .collect::<Vec<_>>()
9450    });
9451
9452    let mut request =
9453        cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
9454            let edits = edits.clone();
9455            async move {
9456                Ok(lsp::CompletionItem {
9457                    additional_text_edits: edits,
9458                    ..Default::default()
9459                })
9460            }
9461        });
9462
9463    async move {
9464        request.next().await;
9465    }
9466}
9467
9468pub(crate) fn update_test_language_settings(
9469    cx: &mut TestAppContext,
9470    f: impl Fn(&mut AllLanguageSettingsContent),
9471) {
9472    _ = cx.update(|cx| {
9473        cx.update_global(|store: &mut SettingsStore, cx| {
9474            store.update_user_settings::<AllLanguageSettings>(cx, f);
9475        });
9476    });
9477}
9478
9479pub(crate) fn update_test_project_settings(
9480    cx: &mut TestAppContext,
9481    f: impl Fn(&mut ProjectSettings),
9482) {
9483    _ = cx.update(|cx| {
9484        cx.update_global(|store: &mut SettingsStore, cx| {
9485            store.update_user_settings::<ProjectSettings>(cx, f);
9486        });
9487    });
9488}
9489
9490pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
9491    _ = cx.update(|cx| {
9492        let store = SettingsStore::test(cx);
9493        cx.set_global(store);
9494        theme::init(theme::LoadThemes::JustBase, cx);
9495        release_channel::init("0.0.0", cx);
9496        client::init_settings(cx);
9497        language::init(cx);
9498        Project::init_settings(cx);
9499        workspace::init_settings(cx);
9500        crate::init(cx);
9501    });
9502
9503    update_test_language_settings(cx, f);
9504}
9505
9506pub(crate) fn rust_lang() -> Arc<Language> {
9507    Arc::new(Language::new(
9508        LanguageConfig {
9509            name: "Rust".into(),
9510            matcher: LanguageMatcher {
9511                path_suffixes: vec!["rs".to_string()],
9512                ..Default::default()
9513            },
9514            ..Default::default()
9515        },
9516        Some(tree_sitter_rust::language()),
9517    ))
9518}
9519
9520#[track_caller]
9521fn assert_hunk_revert(
9522    not_reverted_text_with_selections: &str,
9523    expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
9524    expected_reverted_text_with_selections: &str,
9525    base_text: &str,
9526    cx: &mut EditorLspTestContext,
9527) {
9528    cx.set_state(not_reverted_text_with_selections);
9529    cx.update_editor(|editor, cx| {
9530        editor
9531            .buffer()
9532            .read(cx)
9533            .as_singleton()
9534            .unwrap()
9535            .update(cx, |buffer, cx| {
9536                buffer.set_diff_base(Some(base_text.to_string()), cx);
9537            });
9538    });
9539    cx.executor().run_until_parked();
9540
9541    let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
9542        let snapshot = editor
9543            .buffer()
9544            .read(cx)
9545            .as_singleton()
9546            .unwrap()
9547            .read(cx)
9548            .snapshot();
9549        let reverted_hunk_statuses = snapshot
9550            .git_diff_hunks_in_row_range(0..u32::MAX)
9551            .map(|hunk| hunk.status())
9552            .collect::<Vec<_>>();
9553
9554        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
9555        reverted_hunk_statuses
9556    });
9557    cx.executor().run_until_parked();
9558    cx.assert_editor_state(expected_reverted_text_with_selections);
9559    assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
9560}