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