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., 2.));
1433        editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1434        assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.));
1435    });
1436}
1437
1438#[gpui::test]
1439async fn test_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    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3674
3675    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3676    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
3677
3678    cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3679    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3680
3681    cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3682    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
3683
3684    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3685    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3686
3687    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3688    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3689}
3690
3691#[gpui::test]
3692async fn test_select_previous(cx: &mut gpui::TestAppContext) {
3693    init_test(cx, |_| {});
3694    {
3695        // `Select previous` without a selection (selects wordwise)
3696        let mut cx = EditorTestContext::new(cx).await;
3697        cx.set_state("abc\nˇabc abc\ndefabc\nabc");
3698
3699        cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3700        cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3701
3702        cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3703        cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
3704
3705        cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3706        cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3707
3708        cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3709        cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
3710
3711        cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3712        cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
3713
3714        cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3715        cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3716    }
3717    {
3718        // `Select previous` with a selection
3719        let mut cx = EditorTestContext::new(cx).await;
3720        cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
3721
3722        cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3723        cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
3724
3725        cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3726        cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
3727
3728        cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3729        cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
3730
3731        cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3732        cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
3733
3734        cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3735        cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
3736
3737        cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3738        cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
3739    }
3740}
3741
3742#[gpui::test]
3743async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
3744    init_test(cx, |_| {});
3745
3746    let language = Arc::new(Language::new(
3747        LanguageConfig::default(),
3748        Some(tree_sitter_rust::language()),
3749    ));
3750
3751    let text = r#"
3752        use mod1::mod2::{mod3, mod4};
3753
3754        fn fn_1(param1: bool, param2: &str) {
3755            let var1 = "text";
3756        }
3757    "#
3758    .unindent();
3759
3760    let buffer =
3761        cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx));
3762    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3763    let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
3764    view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3765        .await;
3766
3767    view.update(cx, |view, cx| {
3768        view.change_selections(None, cx, |s| {
3769            s.select_display_ranges([
3770                DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3771                DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3772                DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3773            ]);
3774        });
3775        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3776    });
3777    assert_eq!(
3778        view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
3779        &[
3780            DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
3781            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3782            DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
3783        ]
3784    );
3785
3786    view.update(cx, |view, cx| {
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, 16)..DisplayPoint::new(0, 28),
3793            DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
3794        ]
3795    );
3796
3797    view.update(cx, |view, cx| {
3798        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3799    });
3800    assert_eq!(
3801        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3802        &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3803    );
3804
3805    // Trying to expand the selected syntax node one more time has no effect.
3806    view.update(cx, |view, cx| {
3807        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3808    });
3809    assert_eq!(
3810        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3811        &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3812    );
3813
3814    view.update(cx, |view, cx| {
3815        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3816    });
3817    assert_eq!(
3818        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3819        &[
3820            DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3821            DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
3822        ]
3823    );
3824
3825    view.update(cx, |view, cx| {
3826        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3827    });
3828    assert_eq!(
3829        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3830        &[
3831            DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
3832            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3833            DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
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, 25)..DisplayPoint::new(0, 25),
3844            DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3845            DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3846        ]
3847    );
3848
3849    // Trying to shrink the selected syntax node one more time has no effect.
3850    view.update(cx, |view, cx| {
3851        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3852    });
3853    assert_eq!(
3854        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3855        &[
3856            DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3857            DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3858            DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3859        ]
3860    );
3861
3862    // Ensure that we keep expanding the selection if the larger selection starts or ends within
3863    // a fold.
3864    view.update(cx, |view, cx| {
3865        view.fold_ranges(
3866            vec![
3867                Point::new(0, 21)..Point::new(0, 24),
3868                Point::new(3, 20)..Point::new(3, 22),
3869            ],
3870            true,
3871            cx,
3872        );
3873        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3874    });
3875    assert_eq!(
3876        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3877        &[
3878            DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3879            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3880            DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
3881        ]
3882    );
3883}
3884
3885#[gpui::test]
3886async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
3887    init_test(cx, |_| {});
3888
3889    let language = Arc::new(
3890        Language::new(
3891            LanguageConfig {
3892                brackets: BracketPairConfig {
3893                    pairs: vec![
3894                        BracketPair {
3895                            start: "{".to_string(),
3896                            end: "}".to_string(),
3897                            close: false,
3898                            newline: true,
3899                        },
3900                        BracketPair {
3901                            start: "(".to_string(),
3902                            end: ")".to_string(),
3903                            close: false,
3904                            newline: true,
3905                        },
3906                    ],
3907                    ..Default::default()
3908                },
3909                ..Default::default()
3910            },
3911            Some(tree_sitter_rust::language()),
3912        )
3913        .with_indents_query(
3914            r#"
3915                (_ "(" ")" @end) @indent
3916                (_ "{" "}" @end) @indent
3917            "#,
3918        )
3919        .unwrap(),
3920    );
3921
3922    let text = "fn a() {}";
3923
3924    let buffer =
3925        cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx));
3926    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3927    let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
3928    editor
3929        .condition(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
3930        .await;
3931
3932    editor.update(cx, |editor, cx| {
3933        editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
3934        editor.newline(&Newline, cx);
3935        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
3936        assert_eq!(
3937            editor.selections.ranges(cx),
3938            &[
3939                Point::new(1, 4)..Point::new(1, 4),
3940                Point::new(3, 4)..Point::new(3, 4),
3941                Point::new(5, 0)..Point::new(5, 0)
3942            ]
3943        );
3944    });
3945}
3946
3947#[gpui::test]
3948async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
3949    init_test(cx, |_| {});
3950
3951    let mut cx = EditorTestContext::new(cx).await;
3952
3953    let language = Arc::new(Language::new(
3954        LanguageConfig {
3955            brackets: BracketPairConfig {
3956                pairs: vec![
3957                    BracketPair {
3958                        start: "{".to_string(),
3959                        end: "}".to_string(),
3960                        close: true,
3961                        newline: true,
3962                    },
3963                    BracketPair {
3964                        start: "(".to_string(),
3965                        end: ")".to_string(),
3966                        close: true,
3967                        newline: true,
3968                    },
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: false,
3979                        newline: true,
3980                    },
3981                    BracketPair {
3982                        start: "\"".to_string(),
3983                        end: "\"".to_string(),
3984                        close: true,
3985                        newline: false,
3986                    },
3987                ],
3988                ..Default::default()
3989            },
3990            autoclose_before: "})]".to_string(),
3991            ..Default::default()
3992        },
3993        Some(tree_sitter_rust::language()),
3994    ));
3995
3996    let registry = Arc::new(LanguageRegistry::test());
3997    registry.add(language.clone());
3998    cx.update_buffer(|buffer, cx| {
3999        buffer.set_language_registry(registry);
4000        buffer.set_language(Some(language), cx);
4001    });
4002
4003    cx.set_state(
4004        &r#"
4005            🏀ˇ
4006            εˇ
4007            ❤️ˇ
4008        "#
4009        .unindent(),
4010    );
4011
4012    // autoclose multiple nested brackets at multiple cursors
4013    cx.update_editor(|view, cx| {
4014        view.handle_input("{", cx);
4015        view.handle_input("{", cx);
4016        view.handle_input("{", cx);
4017    });
4018    cx.assert_editor_state(
4019        &"
4020            🏀{{{ˇ}}}
4021            ε{{{ˇ}}}
4022            ❤️{{{ˇ}}}
4023        "
4024        .unindent(),
4025    );
4026
4027    // insert a different closing bracket
4028    cx.update_editor(|view, cx| {
4029        view.handle_input(")", cx);
4030    });
4031    cx.assert_editor_state(
4032        &"
4033            🏀{{{)ˇ}}}
4034            ε{{{)ˇ}}}
4035            ❤️{{{)ˇ}}}
4036        "
4037        .unindent(),
4038    );
4039
4040    // skip over the auto-closed brackets when typing a closing bracket
4041    cx.update_editor(|view, cx| {
4042        view.move_right(&MoveRight, cx);
4043        view.handle_input("}", cx);
4044        view.handle_input("}", cx);
4045        view.handle_input("}", cx);
4046    });
4047    cx.assert_editor_state(
4048        &"
4049            🏀{{{)}}}}ˇ
4050            ε{{{)}}}}ˇ
4051            ❤️{{{)}}}}ˇ
4052        "
4053        .unindent(),
4054    );
4055
4056    // autoclose multi-character pairs
4057    cx.set_state(
4058        &"
4059            ˇ
4060            ˇ
4061        "
4062        .unindent(),
4063    );
4064    cx.update_editor(|view, cx| {
4065        view.handle_input("/", cx);
4066        view.handle_input("*", cx);
4067    });
4068    cx.assert_editor_state(
4069        &"
4070            /*ˇ */
4071            /*ˇ */
4072        "
4073        .unindent(),
4074    );
4075
4076    // one cursor autocloses a multi-character pair, one cursor
4077    // does not autoclose.
4078    cx.set_state(
4079        &"
40804081            ˇ
4082        "
4083        .unindent(),
4084    );
4085    cx.update_editor(|view, cx| view.handle_input("*", cx));
4086    cx.assert_editor_state(
4087        &"
4088            /*ˇ */
40894090        "
4091        .unindent(),
4092    );
4093
4094    // Don't autoclose if the next character isn't whitespace and isn't
4095    // listed in the language's "autoclose_before" section.
4096    cx.set_state("ˇa b");
4097    cx.update_editor(|view, cx| view.handle_input("{", cx));
4098    cx.assert_editor_state("{ˇa b");
4099
4100    // Don't autoclose if `close` is false for the bracket pair
4101    cx.set_state("ˇ");
4102    cx.update_editor(|view, cx| view.handle_input("[", cx));
4103    cx.assert_editor_state("");
4104
4105    // Surround with brackets if text is selected
4106    cx.set_state("«aˇ» b");
4107    cx.update_editor(|view, cx| view.handle_input("{", cx));
4108    cx.assert_editor_state("{«aˇ»} b");
4109
4110    // Autclose pair where the start and end characters are the same
4111    cx.set_state("");
4112    cx.update_editor(|view, cx| view.handle_input("\"", cx));
4113    cx.assert_editor_state("a\"ˇ\"");
4114    cx.update_editor(|view, cx| view.handle_input("\"", cx));
4115    cx.assert_editor_state("a\"\"ˇ");
4116}
4117
4118#[gpui::test]
4119async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
4120    init_test(cx, |_| {});
4121
4122    let mut cx = EditorTestContext::new(cx).await;
4123
4124    let html_language = Arc::new(
4125        Language::new(
4126            LanguageConfig {
4127                name: "HTML".into(),
4128                brackets: BracketPairConfig {
4129                    pairs: vec![
4130                        BracketPair {
4131                            start: "<".into(),
4132                            end: ">".into(),
4133                            close: true,
4134                            ..Default::default()
4135                        },
4136                        BracketPair {
4137                            start: "{".into(),
4138                            end: "}".into(),
4139                            close: true,
4140                            ..Default::default()
4141                        },
4142                        BracketPair {
4143                            start: "(".into(),
4144                            end: ")".into(),
4145                            close: true,
4146                            ..Default::default()
4147                        },
4148                    ],
4149                    ..Default::default()
4150                },
4151                autoclose_before: "})]>".into(),
4152                ..Default::default()
4153            },
4154            Some(tree_sitter_html::language()),
4155        )
4156        .with_injection_query(
4157            r#"
4158            (script_element
4159                (raw_text) @content
4160                (#set! "language" "javascript"))
4161            "#,
4162        )
4163        .unwrap(),
4164    );
4165
4166    let javascript_language = Arc::new(Language::new(
4167        LanguageConfig {
4168            name: "JavaScript".into(),
4169            brackets: BracketPairConfig {
4170                pairs: vec![
4171                    BracketPair {
4172                        start: "/*".into(),
4173                        end: " */".into(),
4174                        close: true,
4175                        ..Default::default()
4176                    },
4177                    BracketPair {
4178                        start: "{".into(),
4179                        end: "}".into(),
4180                        close: true,
4181                        ..Default::default()
4182                    },
4183                    BracketPair {
4184                        start: "(".into(),
4185                        end: ")".into(),
4186                        close: true,
4187                        ..Default::default()
4188                    },
4189                ],
4190                ..Default::default()
4191            },
4192            autoclose_before: "})]>".into(),
4193            ..Default::default()
4194        },
4195        Some(tree_sitter_typescript::language_tsx()),
4196    ));
4197
4198    let registry = Arc::new(LanguageRegistry::test());
4199    registry.add(html_language.clone());
4200    registry.add(javascript_language.clone());
4201
4202    cx.update_buffer(|buffer, cx| {
4203        buffer.set_language_registry(registry);
4204        buffer.set_language(Some(html_language), cx);
4205    });
4206
4207    cx.set_state(
4208        &r#"
4209            <body>ˇ
4210                <script>
4211                    var x = 1;ˇ
4212                </script>
4213            </body>ˇ
4214        "#
4215        .unindent(),
4216    );
4217
4218    // Precondition: different languages are active at different locations.
4219    cx.update_editor(|editor, cx| {
4220        let snapshot = editor.snapshot(cx);
4221        let cursors = editor.selections.ranges::<usize>(cx);
4222        let languages = cursors
4223            .iter()
4224            .map(|c| snapshot.language_at(c.start).unwrap().name())
4225            .collect::<Vec<_>>();
4226        assert_eq!(
4227            languages,
4228            &["HTML".into(), "JavaScript".into(), "HTML".into()]
4229        );
4230    });
4231
4232    // Angle brackets autoclose in HTML, but not JavaScript.
4233    cx.update_editor(|editor, cx| {
4234        editor.handle_input("<", cx);
4235        editor.handle_input("a", cx);
4236    });
4237    cx.assert_editor_state(
4238        &r#"
4239            <body><aˇ>
4240                <script>
4241                    var x = 1;<aˇ
4242                </script>
4243            </body><aˇ>
4244        "#
4245        .unindent(),
4246    );
4247
4248    // Curly braces and parens autoclose in both HTML and JavaScript.
4249    cx.update_editor(|editor, cx| {
4250        editor.handle_input(" b=", cx);
4251        editor.handle_input("{", cx);
4252        editor.handle_input("c", cx);
4253        editor.handle_input("(", cx);
4254    });
4255    cx.assert_editor_state(
4256        &r#"
4257            <body><a b={c(ˇ)}>
4258                <script>
4259                    var x = 1;<a b={c(ˇ)}
4260                </script>
4261            </body><a b={c(ˇ)}>
4262        "#
4263        .unindent(),
4264    );
4265
4266    // Brackets that were already autoclosed are skipped.
4267    cx.update_editor(|editor, cx| {
4268        editor.handle_input(")", cx);
4269        editor.handle_input("d", cx);
4270        editor.handle_input("}", cx);
4271    });
4272    cx.assert_editor_state(
4273        &r#"
4274            <body><a b={c()d}ˇ>
4275                <script>
4276                    var x = 1;<a b={c()d}ˇ
4277                </script>
4278            </body><a b={c()d}ˇ>
4279        "#
4280        .unindent(),
4281    );
4282    cx.update_editor(|editor, cx| {
4283        editor.handle_input(">", cx);
4284    });
4285    cx.assert_editor_state(
4286        &r#"
4287            <body><a b={c()d}>ˇ
4288                <script>
4289                    var x = 1;<a b={c()d}>ˇ
4290                </script>
4291            </body><a b={c()d}>ˇ
4292        "#
4293        .unindent(),
4294    );
4295
4296    // Reset
4297    cx.set_state(
4298        &r#"
4299            <body>ˇ
4300                <script>
4301                    var x = 1;ˇ
4302                </script>
4303            </body>ˇ
4304        "#
4305        .unindent(),
4306    );
4307
4308    cx.update_editor(|editor, cx| {
4309        editor.handle_input("<", cx);
4310    });
4311    cx.assert_editor_state(
4312        &r#"
4313            <body><ˇ>
4314                <script>
4315                    var x = 1;<ˇ
4316                </script>
4317            </body><ˇ>
4318        "#
4319        .unindent(),
4320    );
4321
4322    // When backspacing, the closing angle brackets are removed.
4323    cx.update_editor(|editor, cx| {
4324        editor.backspace(&Backspace, cx);
4325    });
4326    cx.assert_editor_state(
4327        &r#"
4328            <body>ˇ
4329                <script>
4330                    var x = 1;ˇ
4331                </script>
4332            </body>ˇ
4333        "#
4334        .unindent(),
4335    );
4336
4337    // Block comments autoclose in JavaScript, but not HTML.
4338    cx.update_editor(|editor, cx| {
4339        editor.handle_input("/", cx);
4340        editor.handle_input("*", cx);
4341    });
4342    cx.assert_editor_state(
4343        &r#"
4344            <body>/*ˇ
4345                <script>
4346                    var x = 1;/*ˇ */
4347                </script>
4348            </body>/*ˇ
4349        "#
4350        .unindent(),
4351    );
4352}
4353
4354#[gpui::test]
4355async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
4356    init_test(cx, |_| {});
4357
4358    let mut cx = EditorTestContext::new(cx).await;
4359
4360    let rust_language = Arc::new(
4361        Language::new(
4362            LanguageConfig {
4363                name: "Rust".into(),
4364                brackets: serde_json::from_value(json!([
4365                    { "start": "{", "end": "}", "close": true, "newline": true },
4366                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
4367                ]))
4368                .unwrap(),
4369                autoclose_before: "})]>".into(),
4370                ..Default::default()
4371            },
4372            Some(tree_sitter_rust::language()),
4373        )
4374        .with_override_query("(string_literal) @string")
4375        .unwrap(),
4376    );
4377
4378    let registry = Arc::new(LanguageRegistry::test());
4379    registry.add(rust_language.clone());
4380
4381    cx.update_buffer(|buffer, cx| {
4382        buffer.set_language_registry(registry);
4383        buffer.set_language(Some(rust_language), cx);
4384    });
4385
4386    cx.set_state(
4387        &r#"
4388            let x = ˇ
4389        "#
4390        .unindent(),
4391    );
4392
4393    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
4394    cx.update_editor(|editor, cx| {
4395        editor.handle_input("\"", cx);
4396    });
4397    cx.assert_editor_state(
4398        &r#"
4399            let x = "ˇ"
4400        "#
4401        .unindent(),
4402    );
4403
4404    // Inserting another quotation mark. The cursor moves across the existing
4405    // automatically-inserted quotation mark.
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    // Reset
4417    cx.set_state(
4418        &r#"
4419            let x = ˇ
4420        "#
4421        .unindent(),
4422    );
4423
4424    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
4425    cx.update_editor(|editor, cx| {
4426        editor.handle_input("\"", cx);
4427        editor.handle_input(" ", cx);
4428        editor.move_left(&Default::default(), cx);
4429        editor.handle_input("\\", cx);
4430        editor.handle_input("\"", cx);
4431    });
4432    cx.assert_editor_state(
4433        &r#"
4434            let x = "\"ˇ "
4435        "#
4436        .unindent(),
4437    );
4438
4439    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
4440    // mark. Nothing is inserted.
4441    cx.update_editor(|editor, cx| {
4442        editor.move_right(&Default::default(), cx);
4443        editor.handle_input("\"", cx);
4444    });
4445    cx.assert_editor_state(
4446        &r#"
4447            let x = "\" "ˇ
4448        "#
4449        .unindent(),
4450    );
4451}
4452
4453#[gpui::test]
4454async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
4455    init_test(cx, |_| {});
4456
4457    let language = Arc::new(Language::new(
4458        LanguageConfig {
4459            brackets: BracketPairConfig {
4460                pairs: vec![
4461                    BracketPair {
4462                        start: "{".to_string(),
4463                        end: "}".to_string(),
4464                        close: true,
4465                        newline: true,
4466                    },
4467                    BracketPair {
4468                        start: "/* ".to_string(),
4469                        end: "*/".to_string(),
4470                        close: true,
4471                        ..Default::default()
4472                    },
4473                ],
4474                ..Default::default()
4475            },
4476            ..Default::default()
4477        },
4478        Some(tree_sitter_rust::language()),
4479    ));
4480
4481    let text = r#"
4482        a
4483        b
4484        c
4485    "#
4486    .unindent();
4487
4488    let buffer =
4489        cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx));
4490    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4491    let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
4492    view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4493        .await;
4494
4495    view.update(cx, |view, cx| {
4496        view.change_selections(None, cx, |s| {
4497            s.select_display_ranges([
4498                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4499                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4500                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
4501            ])
4502        });
4503
4504        view.handle_input("{", cx);
4505        view.handle_input("{", cx);
4506        view.handle_input("{", cx);
4507        assert_eq!(
4508            view.text(cx),
4509            "
4510                {{{a}}}
4511                {{{b}}}
4512                {{{c}}}
4513            "
4514            .unindent()
4515        );
4516        assert_eq!(
4517            view.selections.display_ranges(cx),
4518            [
4519                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
4520                DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
4521                DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
4522            ]
4523        );
4524
4525        view.undo(&Undo, cx);
4526        view.undo(&Undo, cx);
4527        view.undo(&Undo, cx);
4528        assert_eq!(
4529            view.text(cx),
4530            "
4531                a
4532                b
4533                c
4534            "
4535            .unindent()
4536        );
4537        assert_eq!(
4538            view.selections.display_ranges(cx),
4539            [
4540                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4541                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4542                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4543            ]
4544        );
4545
4546        // Ensure inserting the first character of a multi-byte bracket pair
4547        // doesn't surround the selections with the bracket.
4548        view.handle_input("/", cx);
4549        assert_eq!(
4550            view.text(cx),
4551            "
4552                /
4553                /
4554                /
4555            "
4556            .unindent()
4557        );
4558        assert_eq!(
4559            view.selections.display_ranges(cx),
4560            [
4561                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4562                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4563                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4564            ]
4565        );
4566
4567        view.undo(&Undo, cx);
4568        assert_eq!(
4569            view.text(cx),
4570            "
4571                a
4572                b
4573                c
4574            "
4575            .unindent()
4576        );
4577        assert_eq!(
4578            view.selections.display_ranges(cx),
4579            [
4580                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4581                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4582                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4583            ]
4584        );
4585
4586        // Ensure inserting the last character of a multi-byte bracket pair
4587        // doesn't surround the selections with the bracket.
4588        view.handle_input("*", cx);
4589        assert_eq!(
4590            view.text(cx),
4591            "
4592                *
4593                *
4594                *
4595            "
4596            .unindent()
4597        );
4598        assert_eq!(
4599            view.selections.display_ranges(cx),
4600            [
4601                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4602                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4603                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4604            ]
4605        );
4606    });
4607}
4608
4609#[gpui::test]
4610async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
4611    init_test(cx, |_| {});
4612
4613    let language = Arc::new(Language::new(
4614        LanguageConfig {
4615            brackets: BracketPairConfig {
4616                pairs: vec![BracketPair {
4617                    start: "{".to_string(),
4618                    end: "}".to_string(),
4619                    close: true,
4620                    newline: true,
4621                }],
4622                ..Default::default()
4623            },
4624            autoclose_before: "}".to_string(),
4625            ..Default::default()
4626        },
4627        Some(tree_sitter_rust::language()),
4628    ));
4629
4630    let text = r#"
4631        a
4632        b
4633        c
4634    "#
4635    .unindent();
4636
4637    let buffer =
4638        cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx));
4639    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4640    let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
4641    editor
4642        .condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4643        .await;
4644
4645    editor.update(cx, |editor, cx| {
4646        editor.change_selections(None, cx, |s| {
4647            s.select_ranges([
4648                Point::new(0, 1)..Point::new(0, 1),
4649                Point::new(1, 1)..Point::new(1, 1),
4650                Point::new(2, 1)..Point::new(2, 1),
4651            ])
4652        });
4653
4654        editor.handle_input("{", cx);
4655        editor.handle_input("{", cx);
4656        editor.handle_input("_", cx);
4657        assert_eq!(
4658            editor.text(cx),
4659            "
4660                a{{_}}
4661                b{{_}}
4662                c{{_}}
4663            "
4664            .unindent()
4665        );
4666        assert_eq!(
4667            editor.selections.ranges::<Point>(cx),
4668            [
4669                Point::new(0, 4)..Point::new(0, 4),
4670                Point::new(1, 4)..Point::new(1, 4),
4671                Point::new(2, 4)..Point::new(2, 4)
4672            ]
4673        );
4674
4675        editor.backspace(&Default::default(), cx);
4676        editor.backspace(&Default::default(), cx);
4677        assert_eq!(
4678            editor.text(cx),
4679            "
4680                a{}
4681                b{}
4682                c{}
4683            "
4684            .unindent()
4685        );
4686        assert_eq!(
4687            editor.selections.ranges::<Point>(cx),
4688            [
4689                Point::new(0, 2)..Point::new(0, 2),
4690                Point::new(1, 2)..Point::new(1, 2),
4691                Point::new(2, 2)..Point::new(2, 2)
4692            ]
4693        );
4694
4695        editor.delete_to_previous_word_start(&Default::default(), cx);
4696        assert_eq!(
4697            editor.text(cx),
4698            "
4699                a
4700                b
4701                c
4702            "
4703            .unindent()
4704        );
4705        assert_eq!(
4706            editor.selections.ranges::<Point>(cx),
4707            [
4708                Point::new(0, 1)..Point::new(0, 1),
4709                Point::new(1, 1)..Point::new(1, 1),
4710                Point::new(2, 1)..Point::new(2, 1)
4711            ]
4712        );
4713    });
4714}
4715
4716#[gpui::test]
4717async fn test_snippets(cx: &mut gpui::TestAppContext) {
4718    init_test(cx, |_| {});
4719
4720    let (text, insertion_ranges) = marked_text_ranges(
4721        indoc! {"
4722            a.ˇ b
4723            a.ˇ b
4724            a.ˇ b
4725        "},
4726        false,
4727    );
4728
4729    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
4730    let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
4731
4732    editor.update(cx, |editor, cx| {
4733        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
4734
4735        editor
4736            .insert_snippet(&insertion_ranges, snippet, cx)
4737            .unwrap();
4738
4739        fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
4740            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
4741            assert_eq!(editor.text(cx), expected_text);
4742            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
4743        }
4744
4745        assert(
4746            editor,
4747            cx,
4748            indoc! {"
4749                a.f(«one», two, «three») b
4750                a.f(«one», two, «three») b
4751                a.f(«one», two, «three») b
4752            "},
4753        );
4754
4755        // Can't move earlier than the first tab stop
4756        assert!(!editor.move_to_prev_snippet_tabstop(cx));
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        assert!(editor.move_to_next_snippet_tabstop(cx));
4768        assert(
4769            editor,
4770            cx,
4771            indoc! {"
4772                a.f(one, «two», three) b
4773                a.f(one, «two», three) b
4774                a.f(one, «two», three) b
4775            "},
4776        );
4777
4778        editor.move_to_prev_snippet_tabstop(cx);
4779        assert(
4780            editor,
4781            cx,
4782            indoc! {"
4783                a.f(«one», two, «three») b
4784                a.f(«one», two, «three») b
4785                a.f(«one», two, «three») b
4786            "},
4787        );
4788
4789        assert!(editor.move_to_next_snippet_tabstop(cx));
4790        assert(
4791            editor,
4792            cx,
4793            indoc! {"
4794                a.f(one, «two», three) b
4795                a.f(one, «two», three) b
4796                a.f(one, «two», three) b
4797            "},
4798        );
4799        assert!(editor.move_to_next_snippet_tabstop(cx));
4800        assert(
4801            editor,
4802            cx,
4803            indoc! {"
4804                a.f(one, two, three)ˇ b
4805                a.f(one, two, three)ˇ b
4806                a.f(one, two, three)ˇ b
4807            "},
4808        );
4809
4810        // As soon as the last tab stop is reached, snippet state is gone
4811        editor.move_to_prev_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}
4823
4824#[gpui::test]
4825async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
4826    init_test(cx, |_| {});
4827
4828    let mut language = Language::new(
4829        LanguageConfig {
4830            name: "Rust".into(),
4831            path_suffixes: vec!["rs".to_string()],
4832            ..Default::default()
4833        },
4834        Some(tree_sitter_rust::language()),
4835    );
4836    let mut fake_servers = language
4837        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4838            capabilities: lsp::ServerCapabilities {
4839                document_formatting_provider: Some(lsp::OneOf::Left(true)),
4840                ..Default::default()
4841            },
4842            ..Default::default()
4843        }))
4844        .await;
4845
4846    let fs = FakeFs::new(cx.background());
4847    fs.insert_file("/file.rs", Default::default()).await;
4848
4849    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4850    project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4851    let buffer = project
4852        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4853        .await
4854        .unwrap();
4855
4856    cx.foreground().start_waiting();
4857    let fake_server = fake_servers.next().await.unwrap();
4858
4859    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4860    let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
4861    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4862    assert!(cx.read(|cx| editor.is_dirty(cx)));
4863
4864    let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4865    fake_server
4866        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4867            assert_eq!(
4868                params.text_document.uri,
4869                lsp::Url::from_file_path("/file.rs").unwrap()
4870            );
4871            assert_eq!(params.options.tab_size, 4);
4872            Ok(Some(vec![lsp::TextEdit::new(
4873                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4874                ", ".to_string(),
4875            )]))
4876        })
4877        .next()
4878        .await;
4879    cx.foreground().start_waiting();
4880    save.await.unwrap();
4881    assert_eq!(
4882        editor.read_with(cx, |editor, cx| editor.text(cx)),
4883        "one, two\nthree\n"
4884    );
4885    assert!(!cx.read(|cx| editor.is_dirty(cx)));
4886
4887    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4888    assert!(cx.read(|cx| editor.is_dirty(cx)));
4889
4890    // Ensure we can still save even if formatting hangs.
4891    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4892        assert_eq!(
4893            params.text_document.uri,
4894            lsp::Url::from_file_path("/file.rs").unwrap()
4895        );
4896        futures::future::pending::<()>().await;
4897        unreachable!()
4898    });
4899    let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4900    cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4901    cx.foreground().start_waiting();
4902    save.await.unwrap();
4903    assert_eq!(
4904        editor.read_with(cx, |editor, cx| editor.text(cx)),
4905        "one\ntwo\nthree\n"
4906    );
4907    assert!(!cx.read(|cx| editor.is_dirty(cx)));
4908
4909    // Set rust language override and assert overridden tabsize is sent to language server
4910    update_test_language_settings(cx, |settings| {
4911        settings.languages.insert(
4912            "Rust".into(),
4913            LanguageSettingsContent {
4914                tab_size: NonZeroU32::new(8),
4915                ..Default::default()
4916            },
4917        );
4918    });
4919
4920    let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4921    fake_server
4922        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4923            assert_eq!(
4924                params.text_document.uri,
4925                lsp::Url::from_file_path("/file.rs").unwrap()
4926            );
4927            assert_eq!(params.options.tab_size, 8);
4928            Ok(Some(vec![]))
4929        })
4930        .next()
4931        .await;
4932    cx.foreground().start_waiting();
4933    save.await.unwrap();
4934}
4935
4936#[gpui::test]
4937async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
4938    init_test(cx, |_| {});
4939
4940    let mut language = Language::new(
4941        LanguageConfig {
4942            name: "Rust".into(),
4943            path_suffixes: vec!["rs".to_string()],
4944            ..Default::default()
4945        },
4946        Some(tree_sitter_rust::language()),
4947    );
4948    let mut fake_servers = language
4949        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4950            capabilities: lsp::ServerCapabilities {
4951                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
4952                ..Default::default()
4953            },
4954            ..Default::default()
4955        }))
4956        .await;
4957
4958    let fs = FakeFs::new(cx.background());
4959    fs.insert_file("/file.rs", Default::default()).await;
4960
4961    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4962    project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4963    let buffer = project
4964        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4965        .await
4966        .unwrap();
4967
4968    cx.foreground().start_waiting();
4969    let fake_server = fake_servers.next().await.unwrap();
4970
4971    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4972    let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
4973    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4974    assert!(cx.read(|cx| editor.is_dirty(cx)));
4975
4976    let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4977    fake_server
4978        .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
4979            assert_eq!(
4980                params.text_document.uri,
4981                lsp::Url::from_file_path("/file.rs").unwrap()
4982            );
4983            assert_eq!(params.options.tab_size, 4);
4984            Ok(Some(vec![lsp::TextEdit::new(
4985                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4986                ", ".to_string(),
4987            )]))
4988        })
4989        .next()
4990        .await;
4991    cx.foreground().start_waiting();
4992    save.await.unwrap();
4993    assert_eq!(
4994        editor.read_with(cx, |editor, cx| editor.text(cx)),
4995        "one, two\nthree\n"
4996    );
4997    assert!(!cx.read(|cx| editor.is_dirty(cx)));
4998
4999    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5000    assert!(cx.read(|cx| editor.is_dirty(cx)));
5001
5002    // Ensure we can still save even if formatting hangs.
5003    fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
5004        move |params, _| async move {
5005            assert_eq!(
5006                params.text_document.uri,
5007                lsp::Url::from_file_path("/file.rs").unwrap()
5008            );
5009            futures::future::pending::<()>().await;
5010            unreachable!()
5011        },
5012    );
5013    let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
5014    cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
5015    cx.foreground().start_waiting();
5016    save.await.unwrap();
5017    assert_eq!(
5018        editor.read_with(cx, |editor, cx| editor.text(cx)),
5019        "one\ntwo\nthree\n"
5020    );
5021    assert!(!cx.read(|cx| editor.is_dirty(cx)));
5022
5023    // Set rust language override and assert overridden tabsize is sent to language server
5024    update_test_language_settings(cx, |settings| {
5025        settings.languages.insert(
5026            "Rust".into(),
5027            LanguageSettingsContent {
5028                tab_size: NonZeroU32::new(8),
5029                ..Default::default()
5030            },
5031        );
5032    });
5033
5034    let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
5035    fake_server
5036        .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
5037            assert_eq!(
5038                params.text_document.uri,
5039                lsp::Url::from_file_path("/file.rs").unwrap()
5040            );
5041            assert_eq!(params.options.tab_size, 8);
5042            Ok(Some(vec![]))
5043        })
5044        .next()
5045        .await;
5046    cx.foreground().start_waiting();
5047    save.await.unwrap();
5048}
5049
5050#[gpui::test]
5051async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
5052    init_test(cx, |_| {});
5053
5054    let mut language = Language::new(
5055        LanguageConfig {
5056            name: "Rust".into(),
5057            path_suffixes: vec!["rs".to_string()],
5058            ..Default::default()
5059        },
5060        Some(tree_sitter_rust::language()),
5061    );
5062    let mut fake_servers = language
5063        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
5064            capabilities: lsp::ServerCapabilities {
5065                document_formatting_provider: Some(lsp::OneOf::Left(true)),
5066                ..Default::default()
5067            },
5068            ..Default::default()
5069        }))
5070        .await;
5071
5072    let fs = FakeFs::new(cx.background());
5073    fs.insert_file("/file.rs", Default::default()).await;
5074
5075    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5076    project.update(cx, |project, _| project.languages().add(Arc::new(language)));
5077    let buffer = project
5078        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5079        .await
5080        .unwrap();
5081
5082    cx.foreground().start_waiting();
5083    let fake_server = fake_servers.next().await.unwrap();
5084
5085    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
5086    let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
5087    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5088
5089    let format = editor.update(cx, |editor, cx| {
5090        editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
5091    });
5092    fake_server
5093        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5094            assert_eq!(
5095                params.text_document.uri,
5096                lsp::Url::from_file_path("/file.rs").unwrap()
5097            );
5098            assert_eq!(params.options.tab_size, 4);
5099            Ok(Some(vec![lsp::TextEdit::new(
5100                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5101                ", ".to_string(),
5102            )]))
5103        })
5104        .next()
5105        .await;
5106    cx.foreground().start_waiting();
5107    format.await.unwrap();
5108    assert_eq!(
5109        editor.read_with(cx, |editor, cx| editor.text(cx)),
5110        "one, two\nthree\n"
5111    );
5112
5113    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5114    // Ensure we don't lock if formatting hangs.
5115    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5116        assert_eq!(
5117            params.text_document.uri,
5118            lsp::Url::from_file_path("/file.rs").unwrap()
5119        );
5120        futures::future::pending::<()>().await;
5121        unreachable!()
5122    });
5123    let format = editor.update(cx, |editor, cx| {
5124        editor.perform_format(project, FormatTrigger::Manual, cx)
5125    });
5126    cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
5127    cx.foreground().start_waiting();
5128    format.await.unwrap();
5129    assert_eq!(
5130        editor.read_with(cx, |editor, cx| editor.text(cx)),
5131        "one\ntwo\nthree\n"
5132    );
5133}
5134
5135#[gpui::test]
5136async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
5137    init_test(cx, |_| {});
5138
5139    let mut cx = EditorLspTestContext::new_rust(
5140        lsp::ServerCapabilities {
5141            document_formatting_provider: Some(lsp::OneOf::Left(true)),
5142            ..Default::default()
5143        },
5144        cx,
5145    )
5146    .await;
5147
5148    cx.set_state(indoc! {"
5149        one.twoˇ
5150    "});
5151
5152    // The format request takes a long time. When it completes, it inserts
5153    // a newline and an indent before the `.`
5154    cx.lsp
5155        .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
5156            let executor = cx.background();
5157            async move {
5158                executor.timer(Duration::from_millis(100)).await;
5159                Ok(Some(vec![lsp::TextEdit {
5160                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
5161                    new_text: "\n    ".into(),
5162                }]))
5163            }
5164        });
5165
5166    // Submit a format request.
5167    let format_1 = cx
5168        .update_editor(|editor, cx| editor.format(&Format, cx))
5169        .unwrap();
5170    cx.foreground().run_until_parked();
5171
5172    // Submit a second format request.
5173    let format_2 = cx
5174        .update_editor(|editor, cx| editor.format(&Format, cx))
5175        .unwrap();
5176    cx.foreground().run_until_parked();
5177
5178    // Wait for both format requests to complete
5179    cx.foreground().advance_clock(Duration::from_millis(200));
5180    cx.foreground().start_waiting();
5181    format_1.await.unwrap();
5182    cx.foreground().start_waiting();
5183    format_2.await.unwrap();
5184
5185    // The formatting edits only happens once.
5186    cx.assert_editor_state(indoc! {"
5187        one
5188            .twoˇ
5189    "});
5190}
5191
5192#[gpui::test]
5193async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
5194    init_test(cx, |_| {});
5195
5196    let mut cx = EditorLspTestContext::new_rust(
5197        lsp::ServerCapabilities {
5198            document_formatting_provider: Some(lsp::OneOf::Left(true)),
5199            ..Default::default()
5200        },
5201        cx,
5202    )
5203    .await;
5204
5205    // Set up a buffer white some trailing whitespace and no trailing newline.
5206    cx.set_state(
5207        &[
5208            "one ",   //
5209            "twoˇ",   //
5210            "three ", //
5211            "four",   //
5212        ]
5213        .join("\n"),
5214    );
5215
5216    // Submit a format request.
5217    let format = cx
5218        .update_editor(|editor, cx| editor.format(&Format, cx))
5219        .unwrap();
5220
5221    // Record which buffer changes have been sent to the language server
5222    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
5223    cx.lsp
5224        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
5225            let buffer_changes = buffer_changes.clone();
5226            move |params, _| {
5227                buffer_changes.lock().extend(
5228                    params
5229                        .content_changes
5230                        .into_iter()
5231                        .map(|e| (e.range.unwrap(), e.text)),
5232                );
5233            }
5234        });
5235
5236    // Handle formatting requests to the language server.
5237    cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
5238        let buffer_changes = buffer_changes.clone();
5239        move |_, _| {
5240            // When formatting is requested, trailing whitespace has already been stripped,
5241            // and the trailing newline has already been added.
5242            assert_eq!(
5243                &buffer_changes.lock()[1..],
5244                &[
5245                    (
5246                        lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
5247                        "".into()
5248                    ),
5249                    (
5250                        lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
5251                        "".into()
5252                    ),
5253                    (
5254                        lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
5255                        "\n".into()
5256                    ),
5257                ]
5258            );
5259
5260            // Insert blank lines between each line of the buffer.
5261            async move {
5262                Ok(Some(vec![
5263                    lsp::TextEdit {
5264                        range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
5265                        new_text: "\n".into(),
5266                    },
5267                    lsp::TextEdit {
5268                        range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
5269                        new_text: "\n".into(),
5270                    },
5271                ]))
5272            }
5273        }
5274    });
5275
5276    // After formatting the buffer, the trailing whitespace is stripped,
5277    // a newline is appended, and the edits provided by the language server
5278    // have been applied.
5279    format.await.unwrap();
5280    cx.assert_editor_state(
5281        &[
5282            "one",   //
5283            "",      //
5284            "twoˇ",  //
5285            "",      //
5286            "three", //
5287            "four",  //
5288            "",      //
5289        ]
5290        .join("\n"),
5291    );
5292
5293    // Undoing the formatting undoes the trailing whitespace removal, the
5294    // trailing newline, and the LSP edits.
5295    cx.update_buffer(|buffer, cx| buffer.undo(cx));
5296    cx.assert_editor_state(
5297        &[
5298            "one ",   //
5299            "twoˇ",   //
5300            "three ", //
5301            "four",   //
5302        ]
5303        .join("\n"),
5304    );
5305}
5306
5307#[gpui::test]
5308async fn test_completion(cx: &mut gpui::TestAppContext) {
5309    init_test(cx, |_| {});
5310
5311    let mut cx = EditorLspTestContext::new_rust(
5312        lsp::ServerCapabilities {
5313            completion_provider: Some(lsp::CompletionOptions {
5314                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
5315                resolve_provider: Some(true),
5316                ..Default::default()
5317            }),
5318            ..Default::default()
5319        },
5320        cx,
5321    )
5322    .await;
5323
5324    cx.set_state(indoc! {"
5325        oneˇ
5326        two
5327        three
5328    "});
5329    cx.simulate_keystroke(".");
5330    handle_completion_request(
5331        &mut cx,
5332        indoc! {"
5333            one.|<>
5334            two
5335            three
5336        "},
5337        vec!["first_completion", "second_completion"],
5338    )
5339    .await;
5340    cx.condition(|editor, _| editor.context_menu_visible())
5341        .await;
5342    let apply_additional_edits = cx.update_editor(|editor, cx| {
5343        editor.move_down(&MoveDown, cx);
5344        editor
5345            .confirm_completion(&ConfirmCompletion::default(), cx)
5346            .unwrap()
5347    });
5348    cx.assert_editor_state(indoc! {"
5349        one.second_completionˇ
5350        two
5351        three
5352    "});
5353
5354    handle_resolve_completion_request(
5355        &mut cx,
5356        Some(vec![
5357            (
5358                //This overlaps with the primary completion edit which is
5359                //misbehavior from the LSP spec, test that we filter it out
5360                indoc! {"
5361                    one.second_ˇcompletion
5362                    two
5363                    threeˇ
5364                "},
5365                "overlapping additional edit",
5366            ),
5367            (
5368                indoc! {"
5369                    one.second_completion
5370                    two
5371                    threeˇ
5372                "},
5373                "\nadditional edit",
5374            ),
5375        ]),
5376    )
5377    .await;
5378    apply_additional_edits.await.unwrap();
5379    cx.assert_editor_state(indoc! {"
5380        one.second_completionˇ
5381        two
5382        three
5383        additional edit
5384    "});
5385
5386    cx.set_state(indoc! {"
5387        one.second_completion
5388        twoˇ
5389        threeˇ
5390        additional edit
5391    "});
5392    cx.simulate_keystroke(" ");
5393    assert!(cx.editor(|e, _| e.context_menu.is_none()));
5394    cx.simulate_keystroke("s");
5395    assert!(cx.editor(|e, _| e.context_menu.is_none()));
5396
5397    cx.assert_editor_state(indoc! {"
5398        one.second_completion
5399        two sˇ
5400        three sˇ
5401        additional edit
5402    "});
5403    handle_completion_request(
5404        &mut cx,
5405        indoc! {"
5406            one.second_completion
5407            two s
5408            three <s|>
5409            additional edit
5410        "},
5411        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5412    )
5413    .await;
5414    cx.condition(|editor, _| editor.context_menu_visible())
5415        .await;
5416
5417    cx.simulate_keystroke("i");
5418
5419    handle_completion_request(
5420        &mut cx,
5421        indoc! {"
5422            one.second_completion
5423            two si
5424            three <si|>
5425            additional edit
5426        "},
5427        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5428    )
5429    .await;
5430    cx.condition(|editor, _| editor.context_menu_visible())
5431        .await;
5432
5433    let apply_additional_edits = cx.update_editor(|editor, cx| {
5434        editor
5435            .confirm_completion(&ConfirmCompletion::default(), cx)
5436            .unwrap()
5437    });
5438    cx.assert_editor_state(indoc! {"
5439        one.second_completion
5440        two sixth_completionˇ
5441        three sixth_completionˇ
5442        additional edit
5443    "});
5444
5445    handle_resolve_completion_request(&mut cx, None).await;
5446    apply_additional_edits.await.unwrap();
5447
5448    cx.update(|cx| {
5449        cx.update_global::<SettingsStore, _, _>(|settings, cx| {
5450            settings.update_user_settings::<EditorSettings>(cx, |settings| {
5451                settings.show_completions_on_input = Some(false);
5452            });
5453        })
5454    });
5455    cx.set_state("editorˇ");
5456    cx.simulate_keystroke(".");
5457    assert!(cx.editor(|e, _| e.context_menu.is_none()));
5458    cx.simulate_keystroke("c");
5459    cx.simulate_keystroke("l");
5460    cx.simulate_keystroke("o");
5461    cx.assert_editor_state("editor.cloˇ");
5462    assert!(cx.editor(|e, _| e.context_menu.is_none()));
5463    cx.update_editor(|editor, cx| {
5464        editor.show_completions(&ShowCompletions, cx);
5465    });
5466    handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
5467    cx.condition(|editor, _| editor.context_menu_visible())
5468        .await;
5469    let apply_additional_edits = cx.update_editor(|editor, cx| {
5470        editor
5471            .confirm_completion(&ConfirmCompletion::default(), cx)
5472            .unwrap()
5473    });
5474    cx.assert_editor_state("editor.closeˇ");
5475    handle_resolve_completion_request(&mut cx, None).await;
5476    apply_additional_edits.await.unwrap();
5477}
5478
5479#[gpui::test]
5480async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
5481    init_test(cx, |_| {});
5482    let mut cx = EditorTestContext::new(cx).await;
5483    let language = Arc::new(Language::new(
5484        LanguageConfig {
5485            line_comment: Some("// ".into()),
5486            ..Default::default()
5487        },
5488        Some(tree_sitter_rust::language()),
5489    ));
5490    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
5491
5492    // If multiple selections intersect a line, the line is only toggled once.
5493    cx.set_state(indoc! {"
5494        fn a() {
5495            «//b();
5496            ˇ»// «c();
5497            //ˇ»  d();
5498        }
5499    "});
5500
5501    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5502
5503    cx.assert_editor_state(indoc! {"
5504        fn a() {
5505            «b();
5506            c();
5507            ˇ» d();
5508        }
5509    "});
5510
5511    // The comment prefix is inserted at the same column for every line in a
5512    // selection.
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    // If a selection ends at the beginning of a line, that line is not toggled.
5524    cx.set_selections_state(indoc! {"
5525        fn a() {
5526            // b();
5527            «// c();
5528        ˇ»    //  d();
5529        }
5530    "});
5531
5532    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5533
5534    cx.assert_editor_state(indoc! {"
5535        fn a() {
5536            // b();
5537            «c();
5538        ˇ»    //  d();
5539        }
5540    "});
5541
5542    // If a selection span a single line and is empty, the line is toggled.
5543    cx.set_state(indoc! {"
5544        fn a() {
5545            a();
5546            b();
5547        ˇ
5548        }
5549    "});
5550
5551    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5552
5553    cx.assert_editor_state(indoc! {"
5554        fn a() {
5555            a();
5556            b();
5557        //•ˇ
5558        }
5559    "});
5560
5561    // If a selection span multiple lines, empty lines are not toggled.
5562    cx.set_state(indoc! {"
5563        fn a() {
5564            «a();
5565
5566            c();ˇ»
5567        }
5568    "});
5569
5570    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5571
5572    cx.assert_editor_state(indoc! {"
5573        fn a() {
5574            // «a();
5575
5576            // c();ˇ»
5577        }
5578    "});
5579}
5580
5581#[gpui::test]
5582async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
5583    init_test(cx, |_| {});
5584
5585    let language = Arc::new(Language::new(
5586        LanguageConfig {
5587            line_comment: Some("// ".into()),
5588            ..Default::default()
5589        },
5590        Some(tree_sitter_rust::language()),
5591    ));
5592
5593    let registry = Arc::new(LanguageRegistry::test());
5594    registry.add(language.clone());
5595
5596    let mut cx = EditorTestContext::new(cx).await;
5597    cx.update_buffer(|buffer, cx| {
5598        buffer.set_language_registry(registry);
5599        buffer.set_language(Some(language), cx);
5600    });
5601
5602    let toggle_comments = &ToggleComments {
5603        advance_downwards: true,
5604    };
5605
5606    // Single cursor on one line -> advance
5607    // Cursor moves horizontally 3 characters as well on non-blank line
5608    cx.set_state(indoc!(
5609        "fn a() {
5610             ˇdog();
5611             cat();
5612        }"
5613    ));
5614    cx.update_editor(|editor, cx| {
5615        editor.toggle_comments(toggle_comments, cx);
5616    });
5617    cx.assert_editor_state(indoc!(
5618        "fn a() {
5619             // dog();
5620             catˇ();
5621        }"
5622    ));
5623
5624    // Single selection on one line -> don't advance
5625    cx.set_state(indoc!(
5626        "fn a() {
5627             «dog()ˇ»;
5628             cat();
5629        }"
5630    ));
5631    cx.update_editor(|editor, cx| {
5632        editor.toggle_comments(toggle_comments, cx);
5633    });
5634    cx.assert_editor_state(indoc!(
5635        "fn a() {
5636             // «dog()ˇ»;
5637             cat();
5638        }"
5639    ));
5640
5641    // Multiple cursors on one line -> advance
5642    cx.set_state(indoc!(
5643        "fn a() {
5644             ˇdˇog();
5645             cat();
5646        }"
5647    ));
5648    cx.update_editor(|editor, cx| {
5649        editor.toggle_comments(toggle_comments, cx);
5650    });
5651    cx.assert_editor_state(indoc!(
5652        "fn a() {
5653             // dog();
5654             catˇ(ˇ);
5655        }"
5656    ));
5657
5658    // Multiple cursors on one line, with selection -> don't advance
5659    cx.set_state(indoc!(
5660        "fn a() {
5661             ˇdˇog«()ˇ»;
5662             cat();
5663        }"
5664    ));
5665    cx.update_editor(|editor, cx| {
5666        editor.toggle_comments(toggle_comments, cx);
5667    });
5668    cx.assert_editor_state(indoc!(
5669        "fn a() {
5670             // ˇdˇog«()ˇ»;
5671             cat();
5672        }"
5673    ));
5674
5675    // Single cursor on one line -> advance
5676    // Cursor moves to column 0 on blank line
5677    cx.set_state(indoc!(
5678        "fn a() {
5679             ˇdog();
5680
5681             cat();
5682        }"
5683    ));
5684    cx.update_editor(|editor, cx| {
5685        editor.toggle_comments(toggle_comments, cx);
5686    });
5687    cx.assert_editor_state(indoc!(
5688        "fn a() {
5689             // dog();
5690        ˇ
5691             cat();
5692        }"
5693    ));
5694
5695    // Single cursor on one line -> advance
5696    // Cursor starts and ends at column 0
5697    cx.set_state(indoc!(
5698        "fn a() {
5699         ˇ    dog();
5700             cat();
5701        }"
5702    ));
5703    cx.update_editor(|editor, cx| {
5704        editor.toggle_comments(toggle_comments, cx);
5705    });
5706    cx.assert_editor_state(indoc!(
5707        "fn a() {
5708             // dog();
5709         ˇ    cat();
5710        }"
5711    ));
5712}
5713
5714#[gpui::test]
5715async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
5716    init_test(cx, |_| {});
5717
5718    let mut cx = EditorTestContext::new(cx).await;
5719
5720    let html_language = Arc::new(
5721        Language::new(
5722            LanguageConfig {
5723                name: "HTML".into(),
5724                block_comment: Some(("<!-- ".into(), " -->".into())),
5725                ..Default::default()
5726            },
5727            Some(tree_sitter_html::language()),
5728        )
5729        .with_injection_query(
5730            r#"
5731            (script_element
5732                (raw_text) @content
5733                (#set! "language" "javascript"))
5734            "#,
5735        )
5736        .unwrap(),
5737    );
5738
5739    let javascript_language = Arc::new(Language::new(
5740        LanguageConfig {
5741            name: "JavaScript".into(),
5742            line_comment: Some("// ".into()),
5743            ..Default::default()
5744        },
5745        Some(tree_sitter_typescript::language_tsx()),
5746    ));
5747
5748    let registry = Arc::new(LanguageRegistry::test());
5749    registry.add(html_language.clone());
5750    registry.add(javascript_language.clone());
5751
5752    cx.update_buffer(|buffer, cx| {
5753        buffer.set_language_registry(registry);
5754        buffer.set_language(Some(html_language), cx);
5755    });
5756
5757    // Toggle comments for empty selections
5758    cx.set_state(
5759        &r#"
5760            <p>A</p>ˇ
5761            <p>B</p>ˇ
5762            <p>C</p>ˇ
5763        "#
5764        .unindent(),
5765    );
5766    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5767    cx.assert_editor_state(
5768        &r#"
5769            <!-- <p>A</p>ˇ -->
5770            <!-- <p>B</p>ˇ -->
5771            <!-- <p>C</p>ˇ -->
5772        "#
5773        .unindent(),
5774    );
5775    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5776    cx.assert_editor_state(
5777        &r#"
5778            <p>A</p>ˇ
5779            <p>B</p>ˇ
5780            <p>C</p>ˇ
5781        "#
5782        .unindent(),
5783    );
5784
5785    // Toggle comments for mixture of empty and non-empty selections, where
5786    // multiple selections occupy a given line.
5787    cx.set_state(
5788        &r#"
5789            <p>A«</p>
5790            <p>ˇ»B</p>ˇ
5791            <p>C«</p>
5792            <p>ˇ»D</p>ˇ
5793        "#
5794        .unindent(),
5795    );
5796
5797    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5798    cx.assert_editor_state(
5799        &r#"
5800            <!-- <p>A«</p>
5801            <p>ˇ»B</p>ˇ -->
5802            <!-- <p>C«</p>
5803            <p>ˇ»D</p>ˇ -->
5804        "#
5805        .unindent(),
5806    );
5807    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5808    cx.assert_editor_state(
5809        &r#"
5810            <p>A«</p>
5811            <p>ˇ»B</p>ˇ
5812            <p>C«</p>
5813            <p>ˇ»D</p>ˇ
5814        "#
5815        .unindent(),
5816    );
5817
5818    // Toggle comments when different languages are active for different
5819    // selections.
5820    cx.set_state(
5821        &r#"
5822            ˇ<script>
5823                ˇvar x = new Y();
5824            ˇ</script>
5825        "#
5826        .unindent(),
5827    );
5828    cx.foreground().run_until_parked();
5829    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5830    cx.assert_editor_state(
5831        &r#"
5832            <!-- ˇ<script> -->
5833                // ˇvar x = new Y();
5834            <!-- ˇ</script> -->
5835        "#
5836        .unindent(),
5837    );
5838}
5839
5840#[gpui::test]
5841fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
5842    init_test(cx, |_| {});
5843
5844    let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, sample_text(3, 4, 'a')));
5845    let multibuffer = cx.add_model(|cx| {
5846        let mut multibuffer = MultiBuffer::new(0);
5847        multibuffer.push_excerpts(
5848            buffer.clone(),
5849            [
5850                ExcerptRange {
5851                    context: Point::new(0, 0)..Point::new(0, 4),
5852                    primary: None,
5853                },
5854                ExcerptRange {
5855                    context: Point::new(1, 0)..Point::new(1, 4),
5856                    primary: None,
5857                },
5858            ],
5859            cx,
5860        );
5861        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
5862        multibuffer
5863    });
5864
5865    let view = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
5866    view.update(cx, |view, cx| {
5867        assert_eq!(view.text(cx), "aaaa\nbbbb");
5868        view.change_selections(None, cx, |s| {
5869            s.select_ranges([
5870                Point::new(0, 0)..Point::new(0, 0),
5871                Point::new(1, 0)..Point::new(1, 0),
5872            ])
5873        });
5874
5875        view.handle_input("X", cx);
5876        assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
5877        assert_eq!(
5878            view.selections.ranges(cx),
5879            [
5880                Point::new(0, 1)..Point::new(0, 1),
5881                Point::new(1, 1)..Point::new(1, 1),
5882            ]
5883        );
5884
5885        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
5886        view.change_selections(None, cx, |s| {
5887            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
5888        });
5889        view.backspace(&Default::default(), cx);
5890        assert_eq!(view.text(cx), "Xa\nbbb");
5891        assert_eq!(
5892            view.selections.ranges(cx),
5893            [Point::new(1, 0)..Point::new(1, 0)]
5894        );
5895
5896        view.change_selections(None, cx, |s| {
5897            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
5898        });
5899        view.backspace(&Default::default(), cx);
5900        assert_eq!(view.text(cx), "X\nbb");
5901        assert_eq!(
5902            view.selections.ranges(cx),
5903            [Point::new(0, 1)..Point::new(0, 1)]
5904        );
5905    });
5906}
5907
5908#[gpui::test]
5909fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
5910    init_test(cx, |_| {});
5911
5912    let markers = vec![('[', ']').into(), ('(', ')').into()];
5913    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
5914        indoc! {"
5915            [aaaa
5916            (bbbb]
5917            cccc)",
5918        },
5919        markers.clone(),
5920    );
5921    let excerpt_ranges = markers.into_iter().map(|marker| {
5922        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
5923        ExcerptRange {
5924            context,
5925            primary: None,
5926        }
5927    });
5928    let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, initial_text));
5929    let multibuffer = cx.add_model(|cx| {
5930        let mut multibuffer = MultiBuffer::new(0);
5931        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
5932        multibuffer
5933    });
5934
5935    let view = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
5936    view.update(cx, |view, cx| {
5937        let (expected_text, selection_ranges) = marked_text_ranges(
5938            indoc! {"
5939                aaaa
5940                bˇbbb
5941                bˇbbˇb
5942                cccc"
5943            },
5944            true,
5945        );
5946        assert_eq!(view.text(cx), expected_text);
5947        view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
5948
5949        view.handle_input("X", cx);
5950
5951        let (expected_text, expected_selections) = marked_text_ranges(
5952            indoc! {"
5953                aaaa
5954                bXˇbbXb
5955                bXˇbbXˇb
5956                cccc"
5957            },
5958            false,
5959        );
5960        assert_eq!(view.text(cx), expected_text);
5961        assert_eq!(view.selections.ranges(cx), expected_selections);
5962
5963        view.newline(&Newline, cx);
5964        let (expected_text, expected_selections) = marked_text_ranges(
5965            indoc! {"
5966                aaaa
5967                bX
5968                ˇbbX
5969                b
5970                bX
5971                ˇbbX
5972                ˇb
5973                cccc"
5974            },
5975            false,
5976        );
5977        assert_eq!(view.text(cx), expected_text);
5978        assert_eq!(view.selections.ranges(cx), expected_selections);
5979    });
5980}
5981
5982#[gpui::test]
5983fn test_refresh_selections(cx: &mut TestAppContext) {
5984    init_test(cx, |_| {});
5985
5986    let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, sample_text(3, 4, 'a')));
5987    let mut excerpt1_id = None;
5988    let multibuffer = cx.add_model(|cx| {
5989        let mut multibuffer = MultiBuffer::new(0);
5990        excerpt1_id = multibuffer
5991            .push_excerpts(
5992                buffer.clone(),
5993                [
5994                    ExcerptRange {
5995                        context: Point::new(0, 0)..Point::new(1, 4),
5996                        primary: None,
5997                    },
5998                    ExcerptRange {
5999                        context: Point::new(1, 0)..Point::new(2, 4),
6000                        primary: None,
6001                    },
6002                ],
6003                cx,
6004            )
6005            .into_iter()
6006            .next();
6007        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6008        multibuffer
6009    });
6010
6011    let editor = cx
6012        .add_window(|cx| {
6013            let mut editor = build_editor(multibuffer.clone(), cx);
6014            let snapshot = editor.snapshot(cx);
6015            editor.change_selections(None, cx, |s| {
6016                s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
6017            });
6018            editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
6019            assert_eq!(
6020                editor.selections.ranges(cx),
6021                [
6022                    Point::new(1, 3)..Point::new(1, 3),
6023                    Point::new(2, 1)..Point::new(2, 1),
6024                ]
6025            );
6026            editor
6027        })
6028        .root(cx);
6029
6030    // Refreshing selections is a no-op when excerpts haven't changed.
6031    editor.update(cx, |editor, cx| {
6032        editor.change_selections(None, cx, |s| s.refresh());
6033        assert_eq!(
6034            editor.selections.ranges(cx),
6035            [
6036                Point::new(1, 3)..Point::new(1, 3),
6037                Point::new(2, 1)..Point::new(2, 1),
6038            ]
6039        );
6040    });
6041
6042    multibuffer.update(cx, |multibuffer, cx| {
6043        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6044    });
6045    editor.update(cx, |editor, cx| {
6046        // Removing an excerpt causes the first selection to become degenerate.
6047        assert_eq!(
6048            editor.selections.ranges(cx),
6049            [
6050                Point::new(0, 0)..Point::new(0, 0),
6051                Point::new(0, 1)..Point::new(0, 1)
6052            ]
6053        );
6054
6055        // Refreshing selections will relocate the first selection to the original buffer
6056        // location.
6057        editor.change_selections(None, cx, |s| s.refresh());
6058        assert_eq!(
6059            editor.selections.ranges(cx),
6060            [
6061                Point::new(0, 1)..Point::new(0, 1),
6062                Point::new(0, 3)..Point::new(0, 3)
6063            ]
6064        );
6065        assert!(editor.selections.pending_anchor().is_some());
6066    });
6067}
6068
6069#[gpui::test]
6070fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
6071    init_test(cx, |_| {});
6072
6073    let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, sample_text(3, 4, 'a')));
6074    let mut excerpt1_id = None;
6075    let multibuffer = cx.add_model(|cx| {
6076        let mut multibuffer = MultiBuffer::new(0);
6077        excerpt1_id = multibuffer
6078            .push_excerpts(
6079                buffer.clone(),
6080                [
6081                    ExcerptRange {
6082                        context: Point::new(0, 0)..Point::new(1, 4),
6083                        primary: None,
6084                    },
6085                    ExcerptRange {
6086                        context: Point::new(1, 0)..Point::new(2, 4),
6087                        primary: None,
6088                    },
6089                ],
6090                cx,
6091            )
6092            .into_iter()
6093            .next();
6094        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6095        multibuffer
6096    });
6097
6098    let editor = cx
6099        .add_window(|cx| {
6100            let mut editor = build_editor(multibuffer.clone(), cx);
6101            let snapshot = editor.snapshot(cx);
6102            editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
6103            assert_eq!(
6104                editor.selections.ranges(cx),
6105                [Point::new(1, 3)..Point::new(1, 3)]
6106            );
6107            editor
6108        })
6109        .root(cx);
6110
6111    multibuffer.update(cx, |multibuffer, cx| {
6112        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6113    });
6114    editor.update(cx, |editor, cx| {
6115        assert_eq!(
6116            editor.selections.ranges(cx),
6117            [Point::new(0, 0)..Point::new(0, 0)]
6118        );
6119
6120        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
6121        editor.change_selections(None, cx, |s| s.refresh());
6122        assert_eq!(
6123            editor.selections.ranges(cx),
6124            [Point::new(0, 3)..Point::new(0, 3)]
6125        );
6126        assert!(editor.selections.pending_anchor().is_some());
6127    });
6128}
6129
6130#[gpui::test]
6131async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
6132    init_test(cx, |_| {});
6133
6134    let language = Arc::new(
6135        Language::new(
6136            LanguageConfig {
6137                brackets: BracketPairConfig {
6138                    pairs: vec![
6139                        BracketPair {
6140                            start: "{".to_string(),
6141                            end: "}".to_string(),
6142                            close: true,
6143                            newline: true,
6144                        },
6145                        BracketPair {
6146                            start: "/* ".to_string(),
6147                            end: " */".to_string(),
6148                            close: true,
6149                            newline: true,
6150                        },
6151                    ],
6152                    ..Default::default()
6153                },
6154                ..Default::default()
6155            },
6156            Some(tree_sitter_rust::language()),
6157        )
6158        .with_indents_query("")
6159        .unwrap(),
6160    );
6161
6162    let text = concat!(
6163        "{   }\n",     //
6164        "  x\n",       //
6165        "  /*   */\n", //
6166        "x\n",         //
6167        "{{} }\n",     //
6168    );
6169
6170    let buffer =
6171        cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx));
6172    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
6173    let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
6174    view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6175        .await;
6176
6177    view.update(cx, |view, cx| {
6178        view.change_selections(None, cx, |s| {
6179            s.select_display_ranges([
6180                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
6181                DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
6182                DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
6183            ])
6184        });
6185        view.newline(&Newline, cx);
6186
6187        assert_eq!(
6188            view.buffer().read(cx).read(cx).text(),
6189            concat!(
6190                "{ \n",    // Suppress rustfmt
6191                "\n",      //
6192                "}\n",     //
6193                "  x\n",   //
6194                "  /* \n", //
6195                "  \n",    //
6196                "  */\n",  //
6197                "x\n",     //
6198                "{{} \n",  //
6199                "}\n",     //
6200            )
6201        );
6202    });
6203}
6204
6205#[gpui::test]
6206fn test_highlighted_ranges(cx: &mut TestAppContext) {
6207    init_test(cx, |_| {});
6208
6209    let editor = cx
6210        .add_window(|cx| {
6211            let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
6212            build_editor(buffer.clone(), cx)
6213        })
6214        .root(cx);
6215
6216    editor.update(cx, |editor, cx| {
6217        struct Type1;
6218        struct Type2;
6219
6220        let buffer = editor.buffer.read(cx).snapshot(cx);
6221
6222        let anchor_range =
6223            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
6224
6225        editor.highlight_background::<Type1>(
6226            vec![
6227                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
6228                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
6229                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
6230                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
6231            ],
6232            |_| Color::red(),
6233            cx,
6234        );
6235        editor.highlight_background::<Type2>(
6236            vec![
6237                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
6238                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
6239                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
6240                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
6241            ],
6242            |_| Color::green(),
6243            cx,
6244        );
6245
6246        let snapshot = editor.snapshot(cx);
6247        let mut highlighted_ranges = editor.background_highlights_in_range(
6248            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
6249            &snapshot,
6250            theme::current(cx).as_ref(),
6251        );
6252        // Enforce a consistent ordering based on color without relying on the ordering of the
6253        // highlight's `TypeId` which is non-deterministic.
6254        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
6255        assert_eq!(
6256            highlighted_ranges,
6257            &[
6258                (
6259                    DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
6260                    Color::green(),
6261                ),
6262                (
6263                    DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
6264                    Color::green(),
6265                ),
6266                (
6267                    DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
6268                    Color::red(),
6269                ),
6270                (
6271                    DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6272                    Color::red(),
6273                ),
6274            ]
6275        );
6276        assert_eq!(
6277            editor.background_highlights_in_range(
6278                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
6279                &snapshot,
6280                theme::current(cx).as_ref(),
6281            ),
6282            &[(
6283                DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6284                Color::red(),
6285            )]
6286        );
6287    });
6288}
6289
6290#[gpui::test]
6291async fn test_following(cx: &mut gpui::TestAppContext) {
6292    init_test(cx, |_| {});
6293
6294    let fs = FakeFs::new(cx.background());
6295    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6296
6297    let buffer = project.update(cx, |project, cx| {
6298        let buffer = project
6299            .create_buffer(&sample_text(16, 8, 'a'), None, cx)
6300            .unwrap();
6301        cx.add_model(|cx| MultiBuffer::singleton(buffer, cx))
6302    });
6303    let leader = cx
6304        .add_window(|cx| build_editor(buffer.clone(), cx))
6305        .root(cx);
6306    let follower = cx
6307        .update(|cx| {
6308            cx.add_window(
6309                WindowOptions {
6310                    bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
6311                    ..Default::default()
6312                },
6313                |cx| build_editor(buffer.clone(), cx),
6314            )
6315        })
6316        .root(cx);
6317
6318    let is_still_following = Rc::new(RefCell::new(true));
6319    let follower_edit_event_count = Rc::new(RefCell::new(0));
6320    let pending_update = Rc::new(RefCell::new(None));
6321    follower.update(cx, {
6322        let update = pending_update.clone();
6323        let is_still_following = is_still_following.clone();
6324        let follower_edit_event_count = follower_edit_event_count.clone();
6325        |_, cx| {
6326            cx.subscribe(&leader, move |_, leader, event, cx| {
6327                leader
6328                    .read(cx)
6329                    .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6330            })
6331            .detach();
6332
6333            cx.subscribe(&follower, move |_, _, event, cx| {
6334                if Editor::should_unfollow_on_event(event, cx) {
6335                    *is_still_following.borrow_mut() = false;
6336                }
6337                if let Event::BufferEdited = event {
6338                    *follower_edit_event_count.borrow_mut() += 1;
6339                }
6340            })
6341            .detach();
6342        }
6343    });
6344
6345    // Update the selections only
6346    leader.update(cx, |leader, cx| {
6347        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6348    });
6349    follower
6350        .update(cx, |follower, cx| {
6351            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6352        })
6353        .await
6354        .unwrap();
6355    follower.read_with(cx, |follower, cx| {
6356        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
6357    });
6358    assert_eq!(*is_still_following.borrow(), true);
6359    assert_eq!(*follower_edit_event_count.borrow(), 0);
6360
6361    // Update the scroll position only
6362    leader.update(cx, |leader, cx| {
6363        leader.set_scroll_position(vec2f(1.5, 3.5), cx);
6364    });
6365    follower
6366        .update(cx, |follower, cx| {
6367            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6368        })
6369        .await
6370        .unwrap();
6371    assert_eq!(
6372        follower.update(cx, |follower, cx| follower.scroll_position(cx)),
6373        vec2f(1.5, 3.5)
6374    );
6375    assert_eq!(*is_still_following.borrow(), true);
6376    assert_eq!(*follower_edit_event_count.borrow(), 0);
6377
6378    // Update the selections and scroll position. The follower's scroll position is updated
6379    // via autoscroll, not via the leader's exact scroll position.
6380    leader.update(cx, |leader, cx| {
6381        leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
6382        leader.request_autoscroll(Autoscroll::newest(), cx);
6383        leader.set_scroll_position(vec2f(1.5, 3.5), cx);
6384    });
6385    follower
6386        .update(cx, |follower, cx| {
6387            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6388        })
6389        .await
6390        .unwrap();
6391    follower.update(cx, |follower, cx| {
6392        assert_eq!(follower.scroll_position(cx), vec2f(1.5, 0.0));
6393        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
6394    });
6395    assert_eq!(*is_still_following.borrow(), true);
6396
6397    // Creating a pending selection that precedes another selection
6398    leader.update(cx, |leader, cx| {
6399        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6400        leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
6401    });
6402    follower
6403        .update(cx, |follower, cx| {
6404            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6405        })
6406        .await
6407        .unwrap();
6408    follower.read_with(cx, |follower, cx| {
6409        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
6410    });
6411    assert_eq!(*is_still_following.borrow(), true);
6412
6413    // Extend the pending selection so that it surrounds another selection
6414    leader.update(cx, |leader, cx| {
6415        leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
6416    });
6417    follower
6418        .update(cx, |follower, cx| {
6419            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6420        })
6421        .await
6422        .unwrap();
6423    follower.read_with(cx, |follower, cx| {
6424        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
6425    });
6426
6427    // Scrolling locally breaks the follow
6428    follower.update(cx, |follower, cx| {
6429        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
6430        follower.set_scroll_anchor(
6431            ScrollAnchor {
6432                anchor: top_anchor,
6433                offset: vec2f(0.0, 0.5),
6434            },
6435            cx,
6436        );
6437    });
6438    assert_eq!(*is_still_following.borrow(), false);
6439}
6440
6441#[gpui::test]
6442async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
6443    init_test(cx, |_| {});
6444
6445    let fs = FakeFs::new(cx.background());
6446    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6447    let workspace = cx
6448        .add_window(|cx| Workspace::test_new(project.clone(), cx))
6449        .root(cx);
6450    let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
6451
6452    let leader = pane.update(cx, |_, cx| {
6453        let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
6454        cx.add_view(|cx| build_editor(multibuffer.clone(), cx))
6455    });
6456
6457    // Start following the editor when it has no excerpts.
6458    let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6459    let follower_1 = cx
6460        .update(|cx| {
6461            Editor::from_state_proto(
6462                pane.clone(),
6463                workspace.clone(),
6464                ViewId {
6465                    creator: Default::default(),
6466                    id: 0,
6467                },
6468                &mut state_message,
6469                cx,
6470            )
6471        })
6472        .unwrap()
6473        .await
6474        .unwrap();
6475
6476    let update_message = Rc::new(RefCell::new(None));
6477    follower_1.update(cx, {
6478        let update = update_message.clone();
6479        |_, cx| {
6480            cx.subscribe(&leader, move |_, leader, event, cx| {
6481                leader
6482                    .read(cx)
6483                    .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6484            })
6485            .detach();
6486        }
6487    });
6488
6489    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
6490        (
6491            project
6492                .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
6493                .unwrap(),
6494            project
6495                .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
6496                .unwrap(),
6497        )
6498    });
6499
6500    // Insert some excerpts.
6501    leader.update(cx, |leader, cx| {
6502        leader.buffer.update(cx, |multibuffer, cx| {
6503            let excerpt_ids = multibuffer.push_excerpts(
6504                buffer_1.clone(),
6505                [
6506                    ExcerptRange {
6507                        context: 1..6,
6508                        primary: None,
6509                    },
6510                    ExcerptRange {
6511                        context: 12..15,
6512                        primary: None,
6513                    },
6514                    ExcerptRange {
6515                        context: 0..3,
6516                        primary: None,
6517                    },
6518                ],
6519                cx,
6520            );
6521            multibuffer.insert_excerpts_after(
6522                excerpt_ids[0],
6523                buffer_2.clone(),
6524                [
6525                    ExcerptRange {
6526                        context: 8..12,
6527                        primary: None,
6528                    },
6529                    ExcerptRange {
6530                        context: 0..6,
6531                        primary: None,
6532                    },
6533                ],
6534                cx,
6535            );
6536        });
6537    });
6538
6539    // Apply the update of adding the excerpts.
6540    follower_1
6541        .update(cx, |follower, cx| {
6542            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6543        })
6544        .await
6545        .unwrap();
6546    assert_eq!(
6547        follower_1.read_with(cx, |editor, cx| editor.text(cx)),
6548        leader.read_with(cx, |editor, cx| editor.text(cx))
6549    );
6550    update_message.borrow_mut().take();
6551
6552    // Start following separately after it already has excerpts.
6553    let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6554    let follower_2 = cx
6555        .update(|cx| {
6556            Editor::from_state_proto(
6557                pane.clone(),
6558                workspace.clone(),
6559                ViewId {
6560                    creator: Default::default(),
6561                    id: 0,
6562                },
6563                &mut state_message,
6564                cx,
6565            )
6566        })
6567        .unwrap()
6568        .await
6569        .unwrap();
6570    assert_eq!(
6571        follower_2.read_with(cx, |editor, cx| editor.text(cx)),
6572        leader.read_with(cx, |editor, cx| editor.text(cx))
6573    );
6574
6575    // Remove some excerpts.
6576    leader.update(cx, |leader, cx| {
6577        leader.buffer.update(cx, |multibuffer, cx| {
6578            let excerpt_ids = multibuffer.excerpt_ids();
6579            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
6580            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
6581        });
6582    });
6583
6584    // Apply the update of removing the excerpts.
6585    follower_1
6586        .update(cx, |follower, cx| {
6587            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6588        })
6589        .await
6590        .unwrap();
6591    follower_2
6592        .update(cx, |follower, cx| {
6593            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6594        })
6595        .await
6596        .unwrap();
6597    update_message.borrow_mut().take();
6598    assert_eq!(
6599        follower_1.read_with(cx, |editor, cx| editor.text(cx)),
6600        leader.read_with(cx, |editor, cx| editor.text(cx))
6601    );
6602}
6603
6604#[test]
6605fn test_combine_syntax_and_fuzzy_match_highlights() {
6606    let string = "abcdefghijklmnop";
6607    let syntax_ranges = [
6608        (
6609            0..3,
6610            HighlightStyle {
6611                color: Some(Color::red()),
6612                ..Default::default()
6613            },
6614        ),
6615        (
6616            4..8,
6617            HighlightStyle {
6618                color: Some(Color::green()),
6619                ..Default::default()
6620            },
6621        ),
6622    ];
6623    let match_indices = [4, 6, 7, 8];
6624    assert_eq!(
6625        combine_syntax_and_fuzzy_match_highlights(
6626            string,
6627            Default::default(),
6628            syntax_ranges.into_iter(),
6629            &match_indices,
6630        ),
6631        &[
6632            (
6633                0..3,
6634                HighlightStyle {
6635                    color: Some(Color::red()),
6636                    ..Default::default()
6637                },
6638            ),
6639            (
6640                4..5,
6641                HighlightStyle {
6642                    color: Some(Color::green()),
6643                    weight: Some(fonts::Weight::BOLD),
6644                    ..Default::default()
6645                },
6646            ),
6647            (
6648                5..6,
6649                HighlightStyle {
6650                    color: Some(Color::green()),
6651                    ..Default::default()
6652                },
6653            ),
6654            (
6655                6..8,
6656                HighlightStyle {
6657                    color: Some(Color::green()),
6658                    weight: Some(fonts::Weight::BOLD),
6659                    ..Default::default()
6660                },
6661            ),
6662            (
6663                8..9,
6664                HighlightStyle {
6665                    weight: Some(fonts::Weight::BOLD),
6666                    ..Default::default()
6667                },
6668            ),
6669        ]
6670    );
6671}
6672
6673#[gpui::test]
6674async fn go_to_hunk(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
6675    init_test(cx, |_| {});
6676
6677    let mut cx = EditorTestContext::new(cx).await;
6678
6679    let diff_base = r#"
6680        use some::mod;
6681
6682        const A: u32 = 42;
6683
6684        fn main() {
6685            println!("hello");
6686
6687            println!("world");
6688        }
6689        "#
6690    .unindent();
6691
6692    // Edits are modified, removed, modified, added
6693    cx.set_state(
6694        &r#"
6695        use some::modified;
6696
6697        ˇ
6698        fn main() {
6699            println!("hello there");
6700
6701            println!("around the");
6702            println!("world");
6703        }
6704        "#
6705        .unindent(),
6706    );
6707
6708    cx.set_diff_base(Some(&diff_base));
6709    deterministic.run_until_parked();
6710
6711    cx.update_editor(|editor, cx| {
6712        //Wrap around the bottom of the buffer
6713        for _ in 0..3 {
6714            editor.go_to_hunk(&GoToHunk, cx);
6715        }
6716    });
6717
6718    cx.assert_editor_state(
6719        &r#"
6720        ˇuse some::modified;
6721
6722
6723        fn main() {
6724            println!("hello there");
6725
6726            println!("around the");
6727            println!("world");
6728        }
6729        "#
6730        .unindent(),
6731    );
6732
6733    cx.update_editor(|editor, cx| {
6734        //Wrap around the top of the buffer
6735        for _ in 0..2 {
6736            editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6737        }
6738    });
6739
6740    cx.assert_editor_state(
6741        &r#"
6742        use some::modified;
6743
6744
6745        fn main() {
6746        ˇ    println!("hello there");
6747
6748            println!("around the");
6749            println!("world");
6750        }
6751        "#
6752        .unindent(),
6753    );
6754
6755    cx.update_editor(|editor, cx| {
6756        editor.fold(&Fold, cx);
6757
6758        //Make sure that the fold only gets one hunk
6759        for _ in 0..4 {
6760            editor.go_to_hunk(&GoToHunk, cx);
6761        }
6762    });
6763
6764    cx.assert_editor_state(
6765        &r#"
6766        ˇuse some::modified;
6767
6768
6769        fn main() {
6770            println!("hello there");
6771
6772            println!("around the");
6773            println!("world");
6774        }
6775        "#
6776        .unindent(),
6777    );
6778}
6779
6780#[test]
6781fn test_split_words() {
6782    fn split<'a>(text: &'a str) -> Vec<&'a str> {
6783        split_words(text).collect()
6784    }
6785
6786    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
6787    assert_eq!(split("hello_world"), &["hello_", "world"]);
6788    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
6789    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
6790    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
6791    assert_eq!(split("helloworld"), &["helloworld"]);
6792}
6793
6794#[gpui::test]
6795async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
6796    init_test(cx, |_| {});
6797
6798    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
6799    let mut assert = |before, after| {
6800        let _state_context = cx.set_state(before);
6801        cx.update_editor(|editor, cx| {
6802            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
6803        });
6804        cx.assert_editor_state(after);
6805    };
6806
6807    // Outside bracket jumps to outside of matching bracket
6808    assert("console.logˇ(var);", "console.log(var)ˇ;");
6809    assert("console.log(var)ˇ;", "console.logˇ(var);");
6810
6811    // Inside bracket jumps to inside of matching bracket
6812    assert("console.log(ˇvar);", "console.log(varˇ);");
6813    assert("console.log(varˇ);", "console.log(ˇvar);");
6814
6815    // When outside a bracket and inside, favor jumping to the inside bracket
6816    assert(
6817        "console.log('foo', [1, 2, 3]ˇ);",
6818        "console.log(ˇ'foo', [1, 2, 3]);",
6819    );
6820    assert(
6821        "console.log(ˇ'foo', [1, 2, 3]);",
6822        "console.log('foo', [1, 2, 3]ˇ);",
6823    );
6824
6825    // Bias forward if two options are equally likely
6826    assert(
6827        "let result = curried_fun()ˇ();",
6828        "let result = curried_fun()()ˇ;",
6829    );
6830
6831    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
6832    assert(
6833        indoc! {"
6834            function test() {
6835                console.log('test')ˇ
6836            }"},
6837        indoc! {"
6838            function test() {
6839                console.logˇ('test')
6840            }"},
6841    );
6842}
6843
6844#[gpui::test(iterations = 10)]
6845async fn test_copilot(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
6846    init_test(cx, |_| {});
6847
6848    let (copilot, copilot_lsp) = Copilot::fake(cx);
6849    cx.update(|cx| cx.set_global(copilot));
6850    let mut cx = EditorLspTestContext::new_rust(
6851        lsp::ServerCapabilities {
6852            completion_provider: Some(lsp::CompletionOptions {
6853                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
6854                ..Default::default()
6855            }),
6856            ..Default::default()
6857        },
6858        cx,
6859    )
6860    .await;
6861
6862    // When inserting, ensure autocompletion is favored over Copilot suggestions.
6863    cx.set_state(indoc! {"
6864        oneˇ
6865        two
6866        three
6867    "});
6868    cx.simulate_keystroke(".");
6869    let _ = handle_completion_request(
6870        &mut cx,
6871        indoc! {"
6872            one.|<>
6873            two
6874            three
6875        "},
6876        vec!["completion_a", "completion_b"],
6877    );
6878    handle_copilot_completion_request(
6879        &copilot_lsp,
6880        vec![copilot::request::Completion {
6881            text: "one.copilot1".into(),
6882            range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6883            ..Default::default()
6884        }],
6885        vec![],
6886    );
6887    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6888    cx.update_editor(|editor, cx| {
6889        assert!(editor.context_menu_visible());
6890        assert!(!editor.has_active_copilot_suggestion(cx));
6891
6892        // Confirming a completion inserts it and hides the context menu, without showing
6893        // the copilot suggestion afterwards.
6894        editor
6895            .confirm_completion(&Default::default(), cx)
6896            .unwrap()
6897            .detach();
6898        assert!(!editor.context_menu_visible());
6899        assert!(!editor.has_active_copilot_suggestion(cx));
6900        assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
6901        assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
6902    });
6903
6904    // Ensure Copilot suggestions are shown right away if no autocompletion is available.
6905    cx.set_state(indoc! {"
6906        oneˇ
6907        two
6908        three
6909    "});
6910    cx.simulate_keystroke(".");
6911    let _ = handle_completion_request(
6912        &mut cx,
6913        indoc! {"
6914            one.|<>
6915            two
6916            three
6917        "},
6918        vec![],
6919    );
6920    handle_copilot_completion_request(
6921        &copilot_lsp,
6922        vec![copilot::request::Completion {
6923            text: "one.copilot1".into(),
6924            range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6925            ..Default::default()
6926        }],
6927        vec![],
6928    );
6929    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6930    cx.update_editor(|editor, cx| {
6931        assert!(!editor.context_menu_visible());
6932        assert!(editor.has_active_copilot_suggestion(cx));
6933        assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6934        assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
6935    });
6936
6937    // Reset editor, and ensure autocompletion is still favored over Copilot suggestions.
6938    cx.set_state(indoc! {"
6939        oneˇ
6940        two
6941        three
6942    "});
6943    cx.simulate_keystroke(".");
6944    let _ = handle_completion_request(
6945        &mut cx,
6946        indoc! {"
6947            one.|<>
6948            two
6949            three
6950        "},
6951        vec!["completion_a", "completion_b"],
6952    );
6953    handle_copilot_completion_request(
6954        &copilot_lsp,
6955        vec![copilot::request::Completion {
6956            text: "one.copilot1".into(),
6957            range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6958            ..Default::default()
6959        }],
6960        vec![],
6961    );
6962    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6963    cx.update_editor(|editor, cx| {
6964        assert!(editor.context_menu_visible());
6965        assert!(!editor.has_active_copilot_suggestion(cx));
6966
6967        // When hiding the context menu, the Copilot suggestion becomes visible.
6968        editor.hide_context_menu(cx);
6969        assert!(!editor.context_menu_visible());
6970        assert!(editor.has_active_copilot_suggestion(cx));
6971        assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6972        assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
6973    });
6974
6975    // Ensure existing completion is interpolated when inserting again.
6976    cx.simulate_keystroke("c");
6977    deterministic.run_until_parked();
6978    cx.update_editor(|editor, cx| {
6979        assert!(!editor.context_menu_visible());
6980        assert!(editor.has_active_copilot_suggestion(cx));
6981        assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6982        assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6983    });
6984
6985    // After debouncing, new Copilot completions should be requested.
6986    handle_copilot_completion_request(
6987        &copilot_lsp,
6988        vec![copilot::request::Completion {
6989            text: "one.copilot2".into(),
6990            range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
6991            ..Default::default()
6992        }],
6993        vec![],
6994    );
6995    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6996    cx.update_editor(|editor, cx| {
6997        assert!(!editor.context_menu_visible());
6998        assert!(editor.has_active_copilot_suggestion(cx));
6999        assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7000        assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7001
7002        // Canceling should remove the active Copilot suggestion.
7003        editor.cancel(&Default::default(), cx);
7004        assert!(!editor.has_active_copilot_suggestion(cx));
7005        assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
7006        assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7007
7008        // After canceling, tabbing shouldn't insert the previously shown suggestion.
7009        editor.tab(&Default::default(), cx);
7010        assert!(!editor.has_active_copilot_suggestion(cx));
7011        assert_eq!(editor.display_text(cx), "one.c   \ntwo\nthree\n");
7012        assert_eq!(editor.text(cx), "one.c   \ntwo\nthree\n");
7013
7014        // When undoing the previously active suggestion is shown again.
7015        editor.undo(&Default::default(), cx);
7016        assert!(editor.has_active_copilot_suggestion(cx));
7017        assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7018        assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7019    });
7020
7021    // If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
7022    cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
7023    cx.update_editor(|editor, cx| {
7024        assert!(editor.has_active_copilot_suggestion(cx));
7025        assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7026        assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7027
7028        // Tabbing when there is an active suggestion inserts it.
7029        editor.tab(&Default::default(), cx);
7030        assert!(!editor.has_active_copilot_suggestion(cx));
7031        assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7032        assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
7033
7034        // When undoing the previously active suggestion is shown again.
7035        editor.undo(&Default::default(), 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        // Hide suggestion.
7041        editor.cancel(&Default::default(), cx);
7042        assert!(!editor.has_active_copilot_suggestion(cx));
7043        assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
7044        assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7045    });
7046
7047    // If an edit occurs outside of this editor but no suggestion is being shown,
7048    // we won't make it visible.
7049    cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
7050    cx.update_editor(|editor, cx| {
7051        assert!(!editor.has_active_copilot_suggestion(cx));
7052        assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
7053        assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
7054    });
7055
7056    // Reset the editor to verify how suggestions behave when tabbing on leading indentation.
7057    cx.update_editor(|editor, cx| {
7058        editor.set_text("fn foo() {\n  \n}", cx);
7059        editor.change_selections(None, cx, |s| {
7060            s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
7061        });
7062    });
7063    handle_copilot_completion_request(
7064        &copilot_lsp,
7065        vec![copilot::request::Completion {
7066            text: "    let x = 4;".into(),
7067            range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
7068            ..Default::default()
7069        }],
7070        vec![],
7071    );
7072
7073    cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
7074    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7075    cx.update_editor(|editor, cx| {
7076        assert!(editor.has_active_copilot_suggestion(cx));
7077        assert_eq!(editor.display_text(cx), "fn foo() {\n    let x = 4;\n}");
7078        assert_eq!(editor.text(cx), "fn foo() {\n  \n}");
7079
7080        // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
7081        editor.tab(&Default::default(), cx);
7082        assert!(editor.has_active_copilot_suggestion(cx));
7083        assert_eq!(editor.text(cx), "fn foo() {\n    \n}");
7084        assert_eq!(editor.display_text(cx), "fn foo() {\n    let x = 4;\n}");
7085
7086        // Tabbing again accepts the suggestion.
7087        editor.tab(&Default::default(), cx);
7088        assert!(!editor.has_active_copilot_suggestion(cx));
7089        assert_eq!(editor.text(cx), "fn foo() {\n    let x = 4;\n}");
7090        assert_eq!(editor.display_text(cx), "fn foo() {\n    let x = 4;\n}");
7091    });
7092}
7093
7094#[gpui::test]
7095async fn test_copilot_completion_invalidation(
7096    deterministic: Arc<Deterministic>,
7097    cx: &mut gpui::TestAppContext,
7098) {
7099    init_test(cx, |_| {});
7100
7101    let (copilot, copilot_lsp) = Copilot::fake(cx);
7102    cx.update(|cx| cx.set_global(copilot));
7103    let mut cx = EditorLspTestContext::new_rust(
7104        lsp::ServerCapabilities {
7105            completion_provider: Some(lsp::CompletionOptions {
7106                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7107                ..Default::default()
7108            }),
7109            ..Default::default()
7110        },
7111        cx,
7112    )
7113    .await;
7114
7115    cx.set_state(indoc! {"
7116        one
7117        twˇ
7118        three
7119    "});
7120
7121    handle_copilot_completion_request(
7122        &copilot_lsp,
7123        vec![copilot::request::Completion {
7124            text: "two.foo()".into(),
7125            range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
7126            ..Default::default()
7127        }],
7128        vec![],
7129    );
7130    cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
7131    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7132    cx.update_editor(|editor, cx| {
7133        assert!(editor.has_active_copilot_suggestion(cx));
7134        assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7135        assert_eq!(editor.text(cx), "one\ntw\nthree\n");
7136
7137        editor.backspace(&Default::default(), cx);
7138        assert!(editor.has_active_copilot_suggestion(cx));
7139        assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7140        assert_eq!(editor.text(cx), "one\nt\nthree\n");
7141
7142        editor.backspace(&Default::default(), cx);
7143        assert!(editor.has_active_copilot_suggestion(cx));
7144        assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7145        assert_eq!(editor.text(cx), "one\n\nthree\n");
7146
7147        // Deleting across the original suggestion range invalidates it.
7148        editor.backspace(&Default::default(), cx);
7149        assert!(!editor.has_active_copilot_suggestion(cx));
7150        assert_eq!(editor.display_text(cx), "one\nthree\n");
7151        assert_eq!(editor.text(cx), "one\nthree\n");
7152
7153        // Undoing the deletion restores the suggestion.
7154        editor.undo(&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}
7160
7161#[gpui::test]
7162async fn test_copilot_multibuffer(
7163    deterministic: Arc<Deterministic>,
7164    cx: &mut gpui::TestAppContext,
7165) {
7166    init_test(cx, |_| {});
7167
7168    let (copilot, copilot_lsp) = Copilot::fake(cx);
7169    cx.update(|cx| cx.set_global(copilot));
7170
7171    let buffer_1 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "a = 1\nb = 2\n"));
7172    let buffer_2 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "c = 3\nd = 4\n"));
7173    let multibuffer = cx.add_model(|cx| {
7174        let mut multibuffer = MultiBuffer::new(0);
7175        multibuffer.push_excerpts(
7176            buffer_1.clone(),
7177            [ExcerptRange {
7178                context: Point::new(0, 0)..Point::new(2, 0),
7179                primary: None,
7180            }],
7181            cx,
7182        );
7183        multibuffer.push_excerpts(
7184            buffer_2.clone(),
7185            [ExcerptRange {
7186                context: Point::new(0, 0)..Point::new(2, 0),
7187                primary: None,
7188            }],
7189            cx,
7190        );
7191        multibuffer
7192    });
7193    let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
7194
7195    handle_copilot_completion_request(
7196        &copilot_lsp,
7197        vec![copilot::request::Completion {
7198            text: "b = 2 + a".into(),
7199            range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
7200            ..Default::default()
7201        }],
7202        vec![],
7203    );
7204    editor.update(cx, |editor, cx| {
7205        // Ensure copilot suggestions are shown for the first excerpt.
7206        editor.change_selections(None, cx, |s| {
7207            s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
7208        });
7209        editor.next_copilot_suggestion(&Default::default(), cx);
7210    });
7211    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7212    editor.update(cx, |editor, cx| {
7213        assert!(editor.has_active_copilot_suggestion(cx));
7214        assert_eq!(
7215            editor.display_text(cx),
7216            "\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n"
7217        );
7218        assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
7219    });
7220
7221    handle_copilot_completion_request(
7222        &copilot_lsp,
7223        vec![copilot::request::Completion {
7224            text: "d = 4 + c".into(),
7225            range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
7226            ..Default::default()
7227        }],
7228        vec![],
7229    );
7230    editor.update(cx, |editor, cx| {
7231        // Move to another excerpt, ensuring the suggestion gets cleared.
7232        editor.change_selections(None, cx, |s| {
7233            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
7234        });
7235        assert!(!editor.has_active_copilot_suggestion(cx));
7236        assert_eq!(
7237            editor.display_text(cx),
7238            "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n"
7239        );
7240        assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
7241
7242        // Type a character, ensuring we don't even try to interpolate the previous suggestion.
7243        editor.handle_input(" ", cx);
7244        assert!(!editor.has_active_copilot_suggestion(cx));
7245        assert_eq!(
7246            editor.display_text(cx),
7247            "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n"
7248        );
7249        assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
7250    });
7251
7252    // Ensure the new suggestion is displayed when the debounce timeout expires.
7253    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7254    editor.update(cx, |editor, cx| {
7255        assert!(editor.has_active_copilot_suggestion(cx));
7256        assert_eq!(
7257            editor.display_text(cx),
7258            "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n"
7259        );
7260        assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
7261    });
7262}
7263
7264#[gpui::test]
7265async fn test_copilot_disabled_globs(
7266    deterministic: Arc<Deterministic>,
7267    cx: &mut gpui::TestAppContext,
7268) {
7269    init_test(cx, |settings| {
7270        settings
7271            .copilot
7272            .get_or_insert(Default::default())
7273            .disabled_globs = Some(vec![".env*".to_string()]);
7274    });
7275
7276    let (copilot, copilot_lsp) = Copilot::fake(cx);
7277    cx.update(|cx| cx.set_global(copilot));
7278
7279    let fs = FakeFs::new(cx.background());
7280    fs.insert_tree(
7281        "/test",
7282        json!({
7283            ".env": "SECRET=something\n",
7284            "README.md": "hello\n"
7285        }),
7286    )
7287    .await;
7288    let project = Project::test(fs, ["/test".as_ref()], cx).await;
7289
7290    let private_buffer = project
7291        .update(cx, |project, cx| {
7292            project.open_local_buffer("/test/.env", cx)
7293        })
7294        .await
7295        .unwrap();
7296    let public_buffer = project
7297        .update(cx, |project, cx| {
7298            project.open_local_buffer("/test/README.md", cx)
7299        })
7300        .await
7301        .unwrap();
7302
7303    let multibuffer = cx.add_model(|cx| {
7304        let mut multibuffer = MultiBuffer::new(0);
7305        multibuffer.push_excerpts(
7306            private_buffer.clone(),
7307            [ExcerptRange {
7308                context: Point::new(0, 0)..Point::new(1, 0),
7309                primary: None,
7310            }],
7311            cx,
7312        );
7313        multibuffer.push_excerpts(
7314            public_buffer.clone(),
7315            [ExcerptRange {
7316                context: Point::new(0, 0)..Point::new(1, 0),
7317                primary: None,
7318            }],
7319            cx,
7320        );
7321        multibuffer
7322    });
7323    let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
7324
7325    let mut copilot_requests = copilot_lsp
7326        .handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
7327            Ok(copilot::request::GetCompletionsResult {
7328                completions: vec![copilot::request::Completion {
7329                    text: "next line".into(),
7330                    range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7331                    ..Default::default()
7332                }],
7333            })
7334        });
7335
7336    editor.update(cx, |editor, cx| {
7337        editor.change_selections(None, cx, |selections| {
7338            selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
7339        });
7340        editor.next_copilot_suggestion(&Default::default(), cx);
7341    });
7342
7343    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7344    assert!(copilot_requests.try_next().is_err());
7345
7346    editor.update(cx, |editor, cx| {
7347        editor.change_selections(None, cx, |s| {
7348            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
7349        });
7350        editor.next_copilot_suggestion(&Default::default(), cx);
7351    });
7352
7353    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7354    assert!(copilot_requests.try_next().is_ok());
7355}
7356
7357#[gpui::test]
7358async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
7359    init_test(cx, |_| {});
7360
7361    let mut language = Language::new(
7362        LanguageConfig {
7363            name: "Rust".into(),
7364            path_suffixes: vec!["rs".to_string()],
7365            brackets: BracketPairConfig {
7366                pairs: vec![BracketPair {
7367                    start: "{".to_string(),
7368                    end: "}".to_string(),
7369                    close: true,
7370                    newline: true,
7371                }],
7372                disabled_scopes_by_bracket_ix: Vec::new(),
7373            },
7374            ..Default::default()
7375        },
7376        Some(tree_sitter_rust::language()),
7377    );
7378    let mut fake_servers = language
7379        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7380            capabilities: lsp::ServerCapabilities {
7381                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
7382                    first_trigger_character: "{".to_string(),
7383                    more_trigger_character: None,
7384                }),
7385                ..Default::default()
7386            },
7387            ..Default::default()
7388        }))
7389        .await;
7390
7391    let fs = FakeFs::new(cx.background());
7392    fs.insert_tree(
7393        "/a",
7394        json!({
7395            "main.rs": "fn main() { let a = 5; }",
7396            "other.rs": "// Test file",
7397        }),
7398    )
7399    .await;
7400    let project = Project::test(fs, ["/a".as_ref()], cx).await;
7401    project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7402    let workspace = cx
7403        .add_window(|cx| Workspace::test_new(project.clone(), cx))
7404        .root(cx);
7405    let worktree_id = workspace.update(cx, |workspace, cx| {
7406        workspace.project().read_with(cx, |project, cx| {
7407            project.worktrees(cx).next().unwrap().read(cx).id()
7408        })
7409    });
7410
7411    let buffer = project
7412        .update(cx, |project, cx| {
7413            project.open_local_buffer("/a/main.rs", cx)
7414        })
7415        .await
7416        .unwrap();
7417    cx.foreground().run_until_parked();
7418    cx.foreground().start_waiting();
7419    let fake_server = fake_servers.next().await.unwrap();
7420    let editor_handle = workspace
7421        .update(cx, |workspace, cx| {
7422            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
7423        })
7424        .await
7425        .unwrap()
7426        .downcast::<Editor>()
7427        .unwrap();
7428
7429    fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
7430        assert_eq!(
7431            params.text_document_position.text_document.uri,
7432            lsp::Url::from_file_path("/a/main.rs").unwrap(),
7433        );
7434        assert_eq!(
7435            params.text_document_position.position,
7436            lsp::Position::new(0, 21),
7437        );
7438
7439        Ok(Some(vec![lsp::TextEdit {
7440            new_text: "]".to_string(),
7441            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
7442        }]))
7443    });
7444
7445    editor_handle.update(cx, |editor, cx| {
7446        cx.focus(&editor_handle);
7447        editor.change_selections(None, cx, |s| {
7448            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
7449        });
7450        editor.handle_input("{", cx);
7451    });
7452
7453    cx.foreground().run_until_parked();
7454
7455    buffer.read_with(cx, |buffer, _| {
7456        assert_eq!(
7457            buffer.text(),
7458            "fn main() { let a = {5}; }",
7459            "No extra braces from on type formatting should appear in the buffer"
7460        )
7461    });
7462}
7463
7464#[gpui::test]
7465async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
7466    init_test(cx, |_| {});
7467
7468    let language_name: Arc<str> = "Rust".into();
7469    let mut language = Language::new(
7470        LanguageConfig {
7471            name: Arc::clone(&language_name),
7472            path_suffixes: vec!["rs".to_string()],
7473            ..Default::default()
7474        },
7475        Some(tree_sitter_rust::language()),
7476    );
7477
7478    let server_restarts = Arc::new(AtomicUsize::new(0));
7479    let closure_restarts = Arc::clone(&server_restarts);
7480    let language_server_name = "test language server";
7481    let mut fake_servers = language
7482        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7483            name: language_server_name,
7484            initialization_options: Some(json!({
7485                "testOptionValue": true
7486            })),
7487            initializer: Some(Box::new(move |fake_server| {
7488                let task_restarts = Arc::clone(&closure_restarts);
7489                fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
7490                    task_restarts.fetch_add(1, atomic::Ordering::Release);
7491                    futures::future::ready(Ok(()))
7492                });
7493            })),
7494            ..Default::default()
7495        }))
7496        .await;
7497
7498    let fs = FakeFs::new(cx.background());
7499    fs.insert_tree(
7500        "/a",
7501        json!({
7502            "main.rs": "fn main() { let a = 5; }",
7503            "other.rs": "// Test file",
7504        }),
7505    )
7506    .await;
7507    let project = Project::test(fs, ["/a".as_ref()], cx).await;
7508    project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7509    let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7510    let _buffer = project
7511        .update(cx, |project, cx| {
7512            project.open_local_buffer("/a/main.rs", cx)
7513        })
7514        .await
7515        .unwrap();
7516    let _fake_server = fake_servers.next().await.unwrap();
7517    update_test_language_settings(cx, |language_settings| {
7518        language_settings.languages.insert(
7519            Arc::clone(&language_name),
7520            LanguageSettingsContent {
7521                tab_size: NonZeroU32::new(8),
7522                ..Default::default()
7523            },
7524        );
7525    });
7526    cx.foreground().run_until_parked();
7527    assert_eq!(
7528        server_restarts.load(atomic::Ordering::Acquire),
7529        0,
7530        "Should not restart LSP server on an unrelated change"
7531    );
7532
7533    update_test_project_settings(cx, |project_settings| {
7534        project_settings.lsp.insert(
7535            "Some other server name".into(),
7536            LspSettings {
7537                initialization_options: Some(json!({
7538                    "some other init value": false
7539                })),
7540            },
7541        );
7542    });
7543    cx.foreground().run_until_parked();
7544    assert_eq!(
7545        server_restarts.load(atomic::Ordering::Acquire),
7546        0,
7547        "Should not restart LSP server on an unrelated LSP settings change"
7548    );
7549
7550    update_test_project_settings(cx, |project_settings| {
7551        project_settings.lsp.insert(
7552            language_server_name.into(),
7553            LspSettings {
7554                initialization_options: Some(json!({
7555                    "anotherInitValue": false
7556                })),
7557            },
7558        );
7559    });
7560    cx.foreground().run_until_parked();
7561    assert_eq!(
7562        server_restarts.load(atomic::Ordering::Acquire),
7563        1,
7564        "Should restart LSP server on a related LSP settings change"
7565    );
7566
7567    update_test_project_settings(cx, |project_settings| {
7568        project_settings.lsp.insert(
7569            language_server_name.into(),
7570            LspSettings {
7571                initialization_options: Some(json!({
7572                    "anotherInitValue": false
7573                })),
7574            },
7575        );
7576    });
7577    cx.foreground().run_until_parked();
7578    assert_eq!(
7579        server_restarts.load(atomic::Ordering::Acquire),
7580        1,
7581        "Should not restart LSP server on a related LSP settings change that is the same"
7582    );
7583
7584    update_test_project_settings(cx, |project_settings| {
7585        project_settings.lsp.insert(
7586            language_server_name.into(),
7587            LspSettings {
7588                initialization_options: None,
7589            },
7590        );
7591    });
7592    cx.foreground().run_until_parked();
7593    assert_eq!(
7594        server_restarts.load(atomic::Ordering::Acquire),
7595        2,
7596        "Should restart LSP server on another related LSP settings change"
7597    );
7598}
7599
7600#[gpui::test]
7601async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
7602    init_test(cx, |_| {});
7603
7604    let mut cx = EditorLspTestContext::new_rust(
7605        lsp::ServerCapabilities {
7606            completion_provider: Some(lsp::CompletionOptions {
7607                trigger_characters: Some(vec![".".to_string()]),
7608                resolve_provider: Some(true),
7609                ..Default::default()
7610            }),
7611            ..Default::default()
7612        },
7613        cx,
7614    )
7615    .await;
7616
7617    cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7618    cx.simulate_keystroke(".");
7619    let completion_item = lsp::CompletionItem {
7620        label: "some".into(),
7621        kind: Some(lsp::CompletionItemKind::SNIPPET),
7622        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7623        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7624            kind: lsp::MarkupKind::Markdown,
7625            value: "```rust\nSome(2)\n```".to_string(),
7626        })),
7627        deprecated: Some(false),
7628        sort_text: Some("fffffff2".to_string()),
7629        filter_text: Some("some".to_string()),
7630        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7631        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7632            range: lsp::Range {
7633                start: lsp::Position {
7634                    line: 0,
7635                    character: 22,
7636                },
7637                end: lsp::Position {
7638                    line: 0,
7639                    character: 22,
7640                },
7641            },
7642            new_text: "Some(2)".to_string(),
7643        })),
7644        additional_text_edits: Some(vec![lsp::TextEdit {
7645            range: lsp::Range {
7646                start: lsp::Position {
7647                    line: 0,
7648                    character: 20,
7649                },
7650                end: lsp::Position {
7651                    line: 0,
7652                    character: 22,
7653                },
7654            },
7655            new_text: "".to_string(),
7656        }]),
7657        ..Default::default()
7658    };
7659
7660    let closure_completion_item = completion_item.clone();
7661    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7662        let task_completion_item = closure_completion_item.clone();
7663        async move {
7664            Ok(Some(lsp::CompletionResponse::Array(vec![
7665                task_completion_item,
7666            ])))
7667        }
7668    });
7669
7670    request.next().await;
7671
7672    cx.condition(|editor, _| editor.context_menu_visible())
7673        .await;
7674    let apply_additional_edits = cx.update_editor(|editor, cx| {
7675        editor
7676            .confirm_completion(&ConfirmCompletion::default(), cx)
7677            .unwrap()
7678    });
7679    cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
7680
7681    cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
7682        let task_completion_item = completion_item.clone();
7683        async move { Ok(task_completion_item) }
7684    })
7685    .next()
7686    .await
7687    .unwrap();
7688    apply_additional_edits.await.unwrap();
7689    cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
7690}
7691
7692#[gpui::test]
7693async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
7694    init_test(cx, |_| {});
7695
7696    let mut cx = EditorLspTestContext::new(
7697        Language::new(
7698            LanguageConfig {
7699                path_suffixes: vec!["jsx".into()],
7700                overrides: [(
7701                    "element".into(),
7702                    LanguageConfigOverride {
7703                        word_characters: Override::Set(['-'].into_iter().collect()),
7704                        ..Default::default()
7705                    },
7706                )]
7707                .into_iter()
7708                .collect(),
7709                ..Default::default()
7710            },
7711            Some(tree_sitter_typescript::language_tsx()),
7712        )
7713        .with_override_query("(jsx_self_closing_element) @element")
7714        .unwrap(),
7715        lsp::ServerCapabilities {
7716            completion_provider: Some(lsp::CompletionOptions {
7717                trigger_characters: Some(vec![":".to_string()]),
7718                ..Default::default()
7719            }),
7720            ..Default::default()
7721        },
7722        cx,
7723    )
7724    .await;
7725
7726    cx.lsp
7727        .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
7728            Ok(Some(lsp::CompletionResponse::Array(vec![
7729                lsp::CompletionItem {
7730                    label: "bg-blue".into(),
7731                    ..Default::default()
7732                },
7733                lsp::CompletionItem {
7734                    label: "bg-red".into(),
7735                    ..Default::default()
7736                },
7737                lsp::CompletionItem {
7738                    label: "bg-yellow".into(),
7739                    ..Default::default()
7740                },
7741            ])))
7742        });
7743
7744    cx.set_state(r#"<p class="bgˇ" />"#);
7745
7746    // Trigger completion when typing a dash, because the dash is an extra
7747    // word character in the 'element' scope, which contains the cursor.
7748    cx.simulate_keystroke("-");
7749    cx.foreground().run_until_parked();
7750    cx.update_editor(|editor, _| {
7751        if let Some(ContextMenu::Completions(menu)) = &editor.context_menu {
7752            assert_eq!(
7753                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7754                &["bg-red", "bg-blue", "bg-yellow"]
7755            );
7756        } else {
7757            panic!("expected completion menu to be open");
7758        }
7759    });
7760
7761    cx.simulate_keystroke("l");
7762    cx.foreground().run_until_parked();
7763    cx.update_editor(|editor, _| {
7764        if let Some(ContextMenu::Completions(menu)) = &editor.context_menu {
7765            assert_eq!(
7766                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7767                &["bg-blue", "bg-yellow"]
7768            );
7769        } else {
7770            panic!("expected completion menu to be open");
7771        }
7772    });
7773
7774    // When filtering completions, consider the character after the '-' to
7775    // be the start of a subword.
7776    cx.set_state(r#"<p class="yelˇ" />"#);
7777    cx.simulate_keystroke("l");
7778    cx.foreground().run_until_parked();
7779    cx.update_editor(|editor, _| {
7780        if let Some(ContextMenu::Completions(menu)) = &editor.context_menu {
7781            assert_eq!(
7782                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7783                &["bg-yellow"]
7784            );
7785        } else {
7786            panic!("expected completion menu to be open");
7787        }
7788    });
7789}
7790
7791fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
7792    let point = DisplayPoint::new(row as u32, column as u32);
7793    point..point
7794}
7795
7796fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
7797    let (text, ranges) = marked_text_ranges(marked_text, true);
7798    assert_eq!(view.text(cx), text);
7799    assert_eq!(
7800        view.selections.ranges(cx),
7801        ranges,
7802        "Assert selections are {}",
7803        marked_text
7804    );
7805}
7806
7807/// Handle completion request passing a marked string specifying where the completion
7808/// should be triggered from using '|' character, what range should be replaced, and what completions
7809/// should be returned using '<' and '>' to delimit the range
7810fn handle_completion_request<'a>(
7811    cx: &mut EditorLspTestContext<'a>,
7812    marked_string: &str,
7813    completions: Vec<&'static str>,
7814) -> impl Future<Output = ()> {
7815    let complete_from_marker: TextRangeMarker = '|'.into();
7816    let replace_range_marker: TextRangeMarker = ('<', '>').into();
7817    let (_, mut marked_ranges) = marked_text_ranges_by(
7818        marked_string,
7819        vec![complete_from_marker.clone(), replace_range_marker.clone()],
7820    );
7821
7822    let complete_from_position =
7823        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
7824    let replace_range =
7825        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
7826
7827    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
7828        let completions = completions.clone();
7829        async move {
7830            assert_eq!(params.text_document_position.text_document.uri, url.clone());
7831            assert_eq!(
7832                params.text_document_position.position,
7833                complete_from_position
7834            );
7835            Ok(Some(lsp::CompletionResponse::Array(
7836                completions
7837                    .iter()
7838                    .map(|completion_text| lsp::CompletionItem {
7839                        label: completion_text.to_string(),
7840                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7841                            range: replace_range,
7842                            new_text: completion_text.to_string(),
7843                        })),
7844                        ..Default::default()
7845                    })
7846                    .collect(),
7847            )))
7848        }
7849    });
7850
7851    async move {
7852        request.next().await;
7853    }
7854}
7855
7856fn handle_resolve_completion_request<'a>(
7857    cx: &mut EditorLspTestContext<'a>,
7858    edits: Option<Vec<(&'static str, &'static str)>>,
7859) -> impl Future<Output = ()> {
7860    let edits = edits.map(|edits| {
7861        edits
7862            .iter()
7863            .map(|(marked_string, new_text)| {
7864                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
7865                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
7866                lsp::TextEdit::new(replace_range, new_text.to_string())
7867            })
7868            .collect::<Vec<_>>()
7869    });
7870
7871    let mut request =
7872        cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
7873            let edits = edits.clone();
7874            async move {
7875                Ok(lsp::CompletionItem {
7876                    additional_text_edits: edits,
7877                    ..Default::default()
7878                })
7879            }
7880        });
7881
7882    async move {
7883        request.next().await;
7884    }
7885}
7886
7887fn handle_copilot_completion_request(
7888    lsp: &lsp::FakeLanguageServer,
7889    completions: Vec<copilot::request::Completion>,
7890    completions_cycling: Vec<copilot::request::Completion>,
7891) {
7892    lsp.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| {
7893        let completions = completions.clone();
7894        async move {
7895            Ok(copilot::request::GetCompletionsResult {
7896                completions: completions.clone(),
7897            })
7898        }
7899    });
7900    lsp.handle_request::<copilot::request::GetCompletionsCycling, _, _>(move |_params, _cx| {
7901        let completions_cycling = completions_cycling.clone();
7902        async move {
7903            Ok(copilot::request::GetCompletionsResult {
7904                completions: completions_cycling.clone(),
7905            })
7906        }
7907    });
7908}
7909
7910pub(crate) fn update_test_language_settings(
7911    cx: &mut TestAppContext,
7912    f: impl Fn(&mut AllLanguageSettingsContent),
7913) {
7914    cx.update(|cx| {
7915        cx.update_global::<SettingsStore, _, _>(|store, cx| {
7916            store.update_user_settings::<AllLanguageSettings>(cx, f);
7917        });
7918    });
7919}
7920
7921pub(crate) fn update_test_project_settings(
7922    cx: &mut TestAppContext,
7923    f: impl Fn(&mut ProjectSettings),
7924) {
7925    cx.update(|cx| {
7926        cx.update_global::<SettingsStore, _, _>(|store, cx| {
7927            store.update_user_settings::<ProjectSettings>(cx, f);
7928        });
7929    });
7930}
7931
7932pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
7933    cx.foreground().forbid_parking();
7934
7935    cx.update(|cx| {
7936        cx.set_global(SettingsStore::test(cx));
7937        theme::init((), cx);
7938        client::init_settings(cx);
7939        language::init(cx);
7940        Project::init_settings(cx);
7941        workspace::init_settings(cx);
7942        crate::init(cx);
7943    });
7944
7945    update_test_language_settings(cx, f);
7946}