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