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