editor_tests.rs

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