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