editor_tests.rs

   1use super::*;
   2use crate::{
   3    scroll::scroll_amount::ScrollAmount,
   4    test::{
   5        assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
   6        editor_test_context::EditorTestContext, select_ranges,
   7    },
   8    JoinLines,
   9};
  10use drag_and_drop::DragAndDrop;
  11use futures::StreamExt;
  12use gpui::{
  13    executor::Deterministic,
  14    geometry::{rect::RectF, vector::vec2f},
  15    platform::{WindowBounds, WindowOptions},
  16    serde_json::{self, json},
  17    TestAppContext,
  18};
  19use indoc::indoc;
  20use language::{
  21    language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent},
  22    BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageRegistry,
  23    Override, Point,
  24};
  25use parking_lot::Mutex;
  26use project::project_settings::{LspSettings, ProjectSettings};
  27use project::FakeFs;
  28use std::sync::atomic;
  29use std::sync::atomic::AtomicUsize;
  30use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
  31use unindent::Unindent;
  32use util::{
  33    assert_set_eq,
  34    test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
  35};
  36use workspace::{
  37    item::{FollowableItem, Item, ItemHandle},
  38    NavigationEntry, ViewId,
  39};
  40
  41#[gpui::test]
  42fn test_edit_events(cx: &mut TestAppContext) {
  43    init_test(cx, |_| {});
  44
  45    let buffer = cx.add_model(|cx| {
  46        let mut buffer = language::Buffer::new(0, cx.model_id() as u64, "123456");
  47        buffer.set_group_interval(Duration::from_secs(1));
  48        buffer
  49    });
  50
  51    let events = Rc::new(RefCell::new(Vec::new()));
  52    let editor1 = cx
  53        .add_window({
  54            let events = events.clone();
  55            |cx| {
  56                cx.subscribe(&cx.handle(), move |_, _, event, _| {
  57                    if matches!(
  58                        event,
  59                        Event::Edited | Event::BufferEdited | Event::DirtyChanged
  60                    ) {
  61                        events.borrow_mut().push(("editor1", event.clone()));
  62                    }
  63                })
  64                .detach();
  65                Editor::for_buffer(buffer.clone(), None, cx)
  66            }
  67        })
  68        .root(cx);
  69    let editor2 = cx
  70        .add_window({
  71            let events = events.clone();
  72            |cx| {
  73                cx.subscribe(&cx.handle(), move |_, _, event, _| {
  74                    if matches!(
  75                        event,
  76                        Event::Edited | Event::BufferEdited | Event::DirtyChanged
  77                    ) {
  78                        events.borrow_mut().push(("editor2", event.clone()));
  79                    }
  80                })
  81                .detach();
  82                Editor::for_buffer(buffer.clone(), None, cx)
  83            }
  84        })
  85        .root(cx);
  86    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  87
  88    // Mutating editor 1 will emit an `Edited` event only for that editor.
  89    editor1.update(cx, |editor, cx| editor.insert("X", cx));
  90    assert_eq!(
  91        mem::take(&mut *events.borrow_mut()),
  92        [
  93            ("editor1", Event::Edited),
  94            ("editor1", Event::BufferEdited),
  95            ("editor2", Event::BufferEdited),
  96            ("editor1", Event::DirtyChanged),
  97            ("editor2", Event::DirtyChanged)
  98        ]
  99    );
 100
 101    // Mutating editor 2 will emit an `Edited` event only for that editor.
 102    editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
 103    assert_eq!(
 104        mem::take(&mut *events.borrow_mut()),
 105        [
 106            ("editor2", Event::Edited),
 107            ("editor1", Event::BufferEdited),
 108            ("editor2", Event::BufferEdited),
 109        ]
 110    );
 111
 112    // Undoing on editor 1 will emit an `Edited` event only for that editor.
 113    editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
 114    assert_eq!(
 115        mem::take(&mut *events.borrow_mut()),
 116        [
 117            ("editor1", Event::Edited),
 118            ("editor1", Event::BufferEdited),
 119            ("editor2", Event::BufferEdited),
 120            ("editor1", Event::DirtyChanged),
 121            ("editor2", Event::DirtyChanged),
 122        ]
 123    );
 124
 125    // Redoing on editor 1 will emit an `Edited` event only for that editor.
 126    editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
 127    assert_eq!(
 128        mem::take(&mut *events.borrow_mut()),
 129        [
 130            ("editor1", Event::Edited),
 131            ("editor1", Event::BufferEdited),
 132            ("editor2", Event::BufferEdited),
 133            ("editor1", Event::DirtyChanged),
 134            ("editor2", Event::DirtyChanged),
 135        ]
 136    );
 137
 138    // Undoing on editor 2 will emit an `Edited` event only for that editor.
 139    editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
 140    assert_eq!(
 141        mem::take(&mut *events.borrow_mut()),
 142        [
 143            ("editor2", Event::Edited),
 144            ("editor1", Event::BufferEdited),
 145            ("editor2", Event::BufferEdited),
 146            ("editor1", Event::DirtyChanged),
 147            ("editor2", Event::DirtyChanged),
 148        ]
 149    );
 150
 151    // Redoing on editor 2 will emit an `Edited` event only for that editor.
 152    editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
 153    assert_eq!(
 154        mem::take(&mut *events.borrow_mut()),
 155        [
 156            ("editor2", Event::Edited),
 157            ("editor1", Event::BufferEdited),
 158            ("editor2", Event::BufferEdited),
 159            ("editor1", Event::DirtyChanged),
 160            ("editor2", Event::DirtyChanged),
 161        ]
 162    );
 163
 164    // No event is emitted when the mutation is a no-op.
 165    editor2.update(cx, |editor, cx| {
 166        editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
 167
 168        editor.backspace(&Backspace, cx);
 169    });
 170    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
 171}
 172
 173#[gpui::test]
 174fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
 175    init_test(cx, |_| {});
 176
 177    let mut now = Instant::now();
 178    let buffer = cx.add_model(|cx| language::Buffer::new(0, cx.model_id() as u64, "123456"));
 179    let group_interval = buffer.read_with(cx, |buffer, _| buffer.transaction_group_interval());
 180    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
 181    let editor = cx
 182        .add_window(|cx| build_editor(buffer.clone(), cx))
 183        .root(cx);
 184
 185    editor.update(cx, |editor, cx| {
 186        editor.start_transaction_at(now, cx);
 187        editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
 188
 189        editor.insert("cd", cx);
 190        editor.end_transaction_at(now, cx);
 191        assert_eq!(editor.text(cx), "12cd56");
 192        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
 193
 194        editor.start_transaction_at(now, cx);
 195        editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
 196        editor.insert("e", cx);
 197        editor.end_transaction_at(now, cx);
 198        assert_eq!(editor.text(cx), "12cde6");
 199        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
 200
 201        now += group_interval + Duration::from_millis(1);
 202        editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
 203
 204        // Simulate an edit in another editor
 205        buffer.update(cx, |buffer, cx| {
 206            buffer.start_transaction_at(now, cx);
 207            buffer.edit([(0..1, "a")], None, cx);
 208            buffer.edit([(1..1, "b")], None, cx);
 209            buffer.end_transaction_at(now, cx);
 210        });
 211
 212        assert_eq!(editor.text(cx), "ab2cde6");
 213        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
 214
 215        // Last transaction happened past the group interval in a different editor.
 216        // Undo it individually and don't restore selections.
 217        editor.undo(&Undo, cx);
 218        assert_eq!(editor.text(cx), "12cde6");
 219        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
 220
 221        // First two transactions happened within the group interval in this editor.
 222        // Undo them together and restore selections.
 223        editor.undo(&Undo, cx);
 224        editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
 225        assert_eq!(editor.text(cx), "123456");
 226        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
 227
 228        // Redo the first two transactions together.
 229        editor.redo(&Redo, cx);
 230        assert_eq!(editor.text(cx), "12cde6");
 231        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
 232
 233        // Redo the last transaction on its own.
 234        editor.redo(&Redo, cx);
 235        assert_eq!(editor.text(cx), "ab2cde6");
 236        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
 237
 238        // Test empty transactions.
 239        editor.start_transaction_at(now, cx);
 240        editor.end_transaction_at(now, cx);
 241        editor.undo(&Undo, cx);
 242        assert_eq!(editor.text(cx), "12cde6");
 243    });
 244}
 245
 246#[gpui::test]
 247fn test_ime_composition(cx: &mut TestAppContext) {
 248    init_test(cx, |_| {});
 249
 250    let buffer = cx.add_model(|cx| {
 251        let mut buffer = language::Buffer::new(0, cx.model_id() as u64, "abcde");
 252        // Ensure automatic grouping doesn't occur.
 253        buffer.set_group_interval(Duration::ZERO);
 254        buffer
 255    });
 256
 257    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
 258    cx.add_window(|cx| {
 259        let mut editor = build_editor(buffer.clone(), cx);
 260
 261        // Start a new IME composition.
 262        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
 263        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
 264        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
 265        assert_eq!(editor.text(cx), "äbcde");
 266        assert_eq!(
 267            editor.marked_text_ranges(cx),
 268            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
 269        );
 270
 271        // Finalize IME composition.
 272        editor.replace_text_in_range(None, "ā", cx);
 273        assert_eq!(editor.text(cx), "ābcde");
 274        assert_eq!(editor.marked_text_ranges(cx), None);
 275
 276        // IME composition edits are grouped and are undone/redone at once.
 277        editor.undo(&Default::default(), cx);
 278        assert_eq!(editor.text(cx), "abcde");
 279        assert_eq!(editor.marked_text_ranges(cx), None);
 280        editor.redo(&Default::default(), cx);
 281        assert_eq!(editor.text(cx), "ābcde");
 282        assert_eq!(editor.marked_text_ranges(cx), None);
 283
 284        // Start a new IME composition.
 285        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
 286        assert_eq!(
 287            editor.marked_text_ranges(cx),
 288            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
 289        );
 290
 291        // Undoing during an IME composition cancels it.
 292        editor.undo(&Default::default(), cx);
 293        assert_eq!(editor.text(cx), "ābcde");
 294        assert_eq!(editor.marked_text_ranges(cx), None);
 295
 296        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
 297        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
 298        assert_eq!(editor.text(cx), "ābcdè");
 299        assert_eq!(
 300            editor.marked_text_ranges(cx),
 301            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
 302        );
 303
 304        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
 305        editor.replace_text_in_range(Some(4..999), "ę", cx);
 306        assert_eq!(editor.text(cx), "ābcdę");
 307        assert_eq!(editor.marked_text_ranges(cx), None);
 308
 309        // Start a new IME composition with multiple cursors.
 310        editor.change_selections(None, cx, |s| {
 311            s.select_ranges([
 312                OffsetUtf16(1)..OffsetUtf16(1),
 313                OffsetUtf16(3)..OffsetUtf16(3),
 314                OffsetUtf16(5)..OffsetUtf16(5),
 315            ])
 316        });
 317        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
 318        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
 319        assert_eq!(
 320            editor.marked_text_ranges(cx),
 321            Some(vec![
 322                OffsetUtf16(0)..OffsetUtf16(3),
 323                OffsetUtf16(4)..OffsetUtf16(7),
 324                OffsetUtf16(8)..OffsetUtf16(11)
 325            ])
 326        );
 327
 328        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
 329        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
 330        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
 331        assert_eq!(
 332            editor.marked_text_ranges(cx),
 333            Some(vec![
 334                OffsetUtf16(1)..OffsetUtf16(2),
 335                OffsetUtf16(5)..OffsetUtf16(6),
 336                OffsetUtf16(9)..OffsetUtf16(10)
 337            ])
 338        );
 339
 340        // Finalize IME composition with multiple cursors.
 341        editor.replace_text_in_range(Some(9..10), "2", cx);
 342        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
 343        assert_eq!(editor.marked_text_ranges(cx), None);
 344
 345        editor
 346    });
 347}
 348
 349#[gpui::test]
 350fn test_selection_with_mouse(cx: &mut TestAppContext) {
 351    init_test(cx, |_| {});
 352
 353    let editor = cx
 354        .add_window(|cx| {
 355            let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
 356            build_editor(buffer, cx)
 357        })
 358        .root(cx);
 359    editor.update(cx, |view, cx| {
 360        view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
 361    });
 362    assert_eq!(
 363        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
 364        [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
 365    );
 366
 367    editor.update(cx, |view, cx| {
 368        view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
 369    });
 370
 371    assert_eq!(
 372        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
 373        [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
 374    );
 375
 376    editor.update(cx, |view, cx| {
 377        view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
 378    });
 379
 380    assert_eq!(
 381        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
 382        [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
 383    );
 384
 385    editor.update(cx, |view, cx| {
 386        view.end_selection(cx);
 387        view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
 388    });
 389
 390    assert_eq!(
 391        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
 392        [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
 393    );
 394
 395    editor.update(cx, |view, cx| {
 396        view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx);
 397        view.update_selection(DisplayPoint::new(0, 0), 0, Vector2F::zero(), cx);
 398    });
 399
 400    assert_eq!(
 401        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
 402        [
 403            DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
 404            DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
 405        ]
 406    );
 407
 408    editor.update(cx, |view, cx| {
 409        view.end_selection(cx);
 410    });
 411
 412    assert_eq!(
 413        editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
 414        [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
 415    );
 416}
 417
 418#[gpui::test]
 419fn test_canceling_pending_selection(cx: &mut TestAppContext) {
 420    init_test(cx, |_| {});
 421
 422    let view = cx
 423        .add_window(|cx| {
 424            let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
 425            build_editor(buffer, cx)
 426        })
 427        .root(cx);
 428
 429    view.update(cx, |view, cx| {
 430        view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
 431        assert_eq!(
 432            view.selections.display_ranges(cx),
 433            [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
 434        );
 435    });
 436
 437    view.update(cx, |view, cx| {
 438        view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
 439        assert_eq!(
 440            view.selections.display_ranges(cx),
 441            [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
 442        );
 443    });
 444
 445    view.update(cx, |view, cx| {
 446        view.cancel(&Cancel, cx);
 447        view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
 448        assert_eq!(
 449            view.selections.display_ranges(cx),
 450            [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
 451        );
 452    });
 453}
 454
 455#[gpui::test]
 456fn test_clone(cx: &mut TestAppContext) {
 457    init_test(cx, |_| {});
 458
 459    let (text, selection_ranges) = marked_text_ranges(
 460        indoc! {"
 461            one
 462            two
 463            threeˇ
 464            four
 465            fiveˇ
 466        "},
 467        true,
 468    );
 469
 470    let editor = cx
 471        .add_window(|cx| {
 472            let buffer = MultiBuffer::build_simple(&text, cx);
 473            build_editor(buffer, cx)
 474        })
 475        .root(cx);
 476
 477    editor.update(cx, |editor, cx| {
 478        editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
 479        editor.fold_ranges(
 480            [
 481                Point::new(1, 0)..Point::new(2, 0),
 482                Point::new(3, 0)..Point::new(4, 0),
 483            ],
 484            true,
 485            cx,
 486        );
 487    });
 488
 489    let cloned_editor = editor
 490        .update(cx, |editor, cx| {
 491            cx.add_window(Default::default(), |cx| editor.clone(cx))
 492        })
 493        .root(cx);
 494
 495    let snapshot = editor.update(cx, |e, cx| e.snapshot(cx));
 496    let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx));
 497
 498    assert_eq!(
 499        cloned_editor.update(cx, |e, cx| e.display_text(cx)),
 500        editor.update(cx, |e, cx| e.display_text(cx))
 501    );
 502    assert_eq!(
 503        cloned_snapshot
 504            .folds_in_range(0..text.len())
 505            .collect::<Vec<_>>(),
 506        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
 507    );
 508    assert_set_eq!(
 509        cloned_editor.read_with(cx, |editor, cx| editor.selections.ranges::<Point>(cx)),
 510        editor.read_with(cx, |editor, cx| editor.selections.ranges(cx))
 511    );
 512    assert_set_eq!(
 513        cloned_editor.update(cx, |e, cx| e.selections.display_ranges(cx)),
 514        editor.update(cx, |e, cx| e.selections.display_ranges(cx))
 515    );
 516}
 517
 518#[gpui::test]
 519async fn test_navigation_history(cx: &mut TestAppContext) {
 520    init_test(cx, |_| {});
 521
 522    cx.set_global(DragAndDrop::<Workspace>::default());
 523    use workspace::item::Item;
 524
 525    let fs = FakeFs::new(cx.background());
 526    let project = Project::test(fs, [], cx).await;
 527    let window = cx.add_window(|cx| Workspace::test_new(project, cx));
 528    let workspace = window.root(cx);
 529    let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 530    window.add_view(cx, |cx| {
 531        let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
 532        let mut editor = build_editor(buffer.clone(), cx);
 533        let handle = cx.handle();
 534        editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
 535
 536        fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
 537            editor.nav_history.as_mut().unwrap().pop_backward(cx)
 538        }
 539
 540        // Move the cursor a small distance.
 541        // Nothing is added to the navigation history.
 542        editor.change_selections(None, cx, |s| {
 543            s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
 544        });
 545        editor.change_selections(None, cx, |s| {
 546            s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
 547        });
 548        assert!(pop_history(&mut editor, cx).is_none());
 549
 550        // Move the cursor a large distance.
 551        // The history can jump back to the previous position.
 552        editor.change_selections(None, cx, |s| {
 553            s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)])
 554        });
 555        let nav_entry = pop_history(&mut editor, cx).unwrap();
 556        editor.navigate(nav_entry.data.unwrap(), cx);
 557        assert_eq!(nav_entry.item.id(), cx.view_id());
 558        assert_eq!(
 559            editor.selections.display_ranges(cx),
 560            &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
 561        );
 562        assert!(pop_history(&mut editor, cx).is_none());
 563
 564        // Move the cursor a small distance via the mouse.
 565        // Nothing is added to the navigation history.
 566        editor.begin_selection(DisplayPoint::new(5, 0), false, 1, cx);
 567        editor.end_selection(cx);
 568        assert_eq!(
 569            editor.selections.display_ranges(cx),
 570            &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
 571        );
 572        assert!(pop_history(&mut editor, cx).is_none());
 573
 574        // Move the cursor a large distance via the mouse.
 575        // The history can jump back to the previous position.
 576        editor.begin_selection(DisplayPoint::new(15, 0), false, 1, cx);
 577        editor.end_selection(cx);
 578        assert_eq!(
 579            editor.selections.display_ranges(cx),
 580            &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)]
 581        );
 582        let nav_entry = pop_history(&mut editor, cx).unwrap();
 583        editor.navigate(nav_entry.data.unwrap(), cx);
 584        assert_eq!(nav_entry.item.id(), cx.view_id());
 585        assert_eq!(
 586            editor.selections.display_ranges(cx),
 587            &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
 588        );
 589        assert!(pop_history(&mut editor, cx).is_none());
 590
 591        // Set scroll position to check later
 592        editor.set_scroll_position(Vector2F::new(5.5, 5.5), cx);
 593        let original_scroll_position = editor.scroll_manager.anchor();
 594
 595        // Jump to the end of the document and adjust scroll
 596        editor.move_to_end(&MoveToEnd, cx);
 597        editor.set_scroll_position(Vector2F::new(-2.5, -0.5), cx);
 598        assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
 599
 600        let nav_entry = pop_history(&mut editor, cx).unwrap();
 601        editor.navigate(nav_entry.data.unwrap(), cx);
 602        assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
 603
 604        // Ensure we don't panic when navigation data contains invalid anchors *and* points.
 605        let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
 606        invalid_anchor.text_anchor.buffer_id = Some(999);
 607        let invalid_point = Point::new(9999, 0);
 608        editor.navigate(
 609            Box::new(NavigationData {
 610                cursor_anchor: invalid_anchor,
 611                cursor_position: invalid_point,
 612                scroll_anchor: ScrollAnchor {
 613                    anchor: invalid_anchor,
 614                    offset: Default::default(),
 615                },
 616                scroll_top_row: invalid_point.row,
 617            }),
 618            cx,
 619        );
 620        assert_eq!(
 621            editor.selections.display_ranges(cx),
 622            &[editor.max_point(cx)..editor.max_point(cx)]
 623        );
 624        assert_eq!(
 625            editor.scroll_position(cx),
 626            vec2f(0., editor.max_point(cx).row() as f32)
 627        );
 628
 629        editor
 630    });
 631}
 632
 633#[gpui::test]
 634fn test_cancel(cx: &mut TestAppContext) {
 635    init_test(cx, |_| {});
 636
 637    let view = cx
 638        .add_window(|cx| {
 639            let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
 640            build_editor(buffer, cx)
 641        })
 642        .root(cx);
 643
 644    view.update(cx, |view, cx| {
 645        view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
 646        view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
 647        view.end_selection(cx);
 648
 649        view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx);
 650        view.update_selection(DisplayPoint::new(0, 3), 0, Vector2F::zero(), cx);
 651        view.end_selection(cx);
 652        assert_eq!(
 653            view.selections.display_ranges(cx),
 654            [
 655                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
 656                DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1),
 657            ]
 658        );
 659    });
 660
 661    view.update(cx, |view, cx| {
 662        view.cancel(&Cancel, cx);
 663        assert_eq!(
 664            view.selections.display_ranges(cx),
 665            [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)]
 666        );
 667    });
 668
 669    view.update(cx, |view, cx| {
 670        view.cancel(&Cancel, cx);
 671        assert_eq!(
 672            view.selections.display_ranges(cx),
 673            [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
 674        );
 675    });
 676}
 677
 678#[gpui::test]
 679fn test_fold_action(cx: &mut TestAppContext) {
 680    init_test(cx, |_| {});
 681
 682    let view = cx
 683        .add_window(|cx| {
 684            let buffer = MultiBuffer::build_simple(
 685                &"
 686                impl Foo {
 687                    // Hello!
 688
 689                    fn a() {
 690                        1
 691                    }
 692
 693                    fn b() {
 694                        2
 695                    }
 696
 697                    fn c() {
 698                        3
 699                    }
 700                }
 701            "
 702                .unindent(),
 703                cx,
 704            );
 705            build_editor(buffer.clone(), cx)
 706        })
 707        .root(cx);
 708
 709    view.update(cx, |view, cx| {
 710        view.change_selections(None, cx, |s| {
 711            s.select_display_ranges([DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)]);
 712        });
 713        view.fold(&Fold, cx);
 714        assert_eq!(
 715            view.display_text(cx),
 716            "
 717                impl Foo {
 718                    // Hello!
 719
 720                    fn a() {
 721                        1
 722                    }
 723
 724                    fn b() {⋯
 725                    }
 726
 727                    fn c() {⋯
 728                    }
 729                }
 730            "
 731            .unindent(),
 732        );
 733
 734        view.fold(&Fold, cx);
 735        assert_eq!(
 736            view.display_text(cx),
 737            "
 738                impl Foo {⋯
 739                }
 740            "
 741            .unindent(),
 742        );
 743
 744        view.unfold_lines(&UnfoldLines, cx);
 745        assert_eq!(
 746            view.display_text(cx),
 747            "
 748                impl Foo {
 749                    // Hello!
 750
 751                    fn a() {
 752                        1
 753                    }
 754
 755                    fn b() {⋯
 756                    }
 757
 758                    fn c() {⋯
 759                    }
 760                }
 761            "
 762            .unindent(),
 763        );
 764
 765        view.unfold_lines(&UnfoldLines, cx);
 766        assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
 767    });
 768}
 769
 770#[gpui::test]
 771fn test_move_cursor(cx: &mut TestAppContext) {
 772    init_test(cx, |_| {});
 773
 774    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 775    let view = cx
 776        .add_window(|cx| build_editor(buffer.clone(), cx))
 777        .root(cx);
 778
 779    buffer.update(cx, |buffer, cx| {
 780        buffer.edit(
 781            vec![
 782                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 783                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 784            ],
 785            None,
 786            cx,
 787        );
 788    });
 789    view.update(cx, |view, cx| {
 790        assert_eq!(
 791            view.selections.display_ranges(cx),
 792            &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
 793        );
 794
 795        view.move_down(&MoveDown, cx);
 796        assert_eq!(
 797            view.selections.display_ranges(cx),
 798            &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
 799        );
 800
 801        view.move_right(&MoveRight, cx);
 802        assert_eq!(
 803            view.selections.display_ranges(cx),
 804            &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)]
 805        );
 806
 807        view.move_left(&MoveLeft, cx);
 808        assert_eq!(
 809            view.selections.display_ranges(cx),
 810            &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
 811        );
 812
 813        view.move_up(&MoveUp, cx);
 814        assert_eq!(
 815            view.selections.display_ranges(cx),
 816            &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
 817        );
 818
 819        view.move_to_end(&MoveToEnd, cx);
 820        assert_eq!(
 821            view.selections.display_ranges(cx),
 822            &[DisplayPoint::new(5, 6)..DisplayPoint::new(5, 6)]
 823        );
 824
 825        view.move_to_beginning(&MoveToBeginning, cx);
 826        assert_eq!(
 827            view.selections.display_ranges(cx),
 828            &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
 829        );
 830
 831        view.change_selections(None, cx, |s| {
 832            s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)]);
 833        });
 834        view.select_to_beginning(&SelectToBeginning, cx);
 835        assert_eq!(
 836            view.selections.display_ranges(cx),
 837            &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 0)]
 838        );
 839
 840        view.select_to_end(&SelectToEnd, cx);
 841        assert_eq!(
 842            view.selections.display_ranges(cx),
 843            &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)]
 844        );
 845    });
 846}
 847
 848#[gpui::test]
 849fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 850    init_test(cx, |_| {});
 851
 852    let view = cx
 853        .add_window(|cx| {
 854            let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", 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            // Enable Prettier formatting for the same buffer, and ensure
5099            // LSP is called instead of Prettier.
5100            prettier_parser_name: Some("test_parser".to_string()),
5101            ..Default::default()
5102        },
5103        Some(tree_sitter_rust::language()),
5104    );
5105    let mut fake_servers = language
5106        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
5107            capabilities: lsp::ServerCapabilities {
5108                document_formatting_provider: Some(lsp::OneOf::Left(true)),
5109                ..Default::default()
5110            },
5111            ..Default::default()
5112        }))
5113        .await;
5114
5115    let fs = FakeFs::new(cx.background());
5116    fs.insert_file("/file.rs", Default::default()).await;
5117
5118    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5119    project.update(cx, |project, _| {
5120        project.enable_test_prettier(&[]);
5121        project.languages().add(Arc::new(language));
5122    });
5123    let buffer = project
5124        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5125        .await
5126        .unwrap();
5127
5128    cx.foreground().start_waiting();
5129    let fake_server = fake_servers.next().await.unwrap();
5130
5131    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
5132    let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
5133    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5134
5135    let format = editor.update(cx, |editor, cx| {
5136        editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
5137    });
5138    fake_server
5139        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5140            assert_eq!(
5141                params.text_document.uri,
5142                lsp::Url::from_file_path("/file.rs").unwrap()
5143            );
5144            assert_eq!(params.options.tab_size, 4);
5145            Ok(Some(vec![lsp::TextEdit::new(
5146                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5147                ", ".to_string(),
5148            )]))
5149        })
5150        .next()
5151        .await;
5152    cx.foreground().start_waiting();
5153    format.await.unwrap();
5154    assert_eq!(
5155        editor.read_with(cx, |editor, cx| editor.text(cx)),
5156        "one, two\nthree\n"
5157    );
5158
5159    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5160    // Ensure we don't lock if formatting hangs.
5161    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5162        assert_eq!(
5163            params.text_document.uri,
5164            lsp::Url::from_file_path("/file.rs").unwrap()
5165        );
5166        futures::future::pending::<()>().await;
5167        unreachable!()
5168    });
5169    let format = editor.update(cx, |editor, cx| {
5170        editor.perform_format(project, FormatTrigger::Manual, cx)
5171    });
5172    cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
5173    cx.foreground().start_waiting();
5174    format.await.unwrap();
5175    assert_eq!(
5176        editor.read_with(cx, |editor, cx| editor.text(cx)),
5177        "one\ntwo\nthree\n"
5178    );
5179}
5180
5181#[gpui::test]
5182async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
5183    init_test(cx, |_| {});
5184
5185    let mut cx = EditorLspTestContext::new_rust(
5186        lsp::ServerCapabilities {
5187            document_formatting_provider: Some(lsp::OneOf::Left(true)),
5188            ..Default::default()
5189        },
5190        cx,
5191    )
5192    .await;
5193
5194    cx.set_state(indoc! {"
5195        one.twoˇ
5196    "});
5197
5198    // The format request takes a long time. When it completes, it inserts
5199    // a newline and an indent before the `.`
5200    cx.lsp
5201        .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
5202            let executor = cx.background();
5203            async move {
5204                executor.timer(Duration::from_millis(100)).await;
5205                Ok(Some(vec![lsp::TextEdit {
5206                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
5207                    new_text: "\n    ".into(),
5208                }]))
5209            }
5210        });
5211
5212    // Submit a format request.
5213    let format_1 = cx
5214        .update_editor(|editor, cx| editor.format(&Format, cx))
5215        .unwrap();
5216    cx.foreground().run_until_parked();
5217
5218    // Submit a second format request.
5219    let format_2 = cx
5220        .update_editor(|editor, cx| editor.format(&Format, cx))
5221        .unwrap();
5222    cx.foreground().run_until_parked();
5223
5224    // Wait for both format requests to complete
5225    cx.foreground().advance_clock(Duration::from_millis(200));
5226    cx.foreground().start_waiting();
5227    format_1.await.unwrap();
5228    cx.foreground().start_waiting();
5229    format_2.await.unwrap();
5230
5231    // The formatting edits only happens once.
5232    cx.assert_editor_state(indoc! {"
5233        one
5234            .twoˇ
5235    "});
5236}
5237
5238#[gpui::test]
5239async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
5240    init_test(cx, |settings| {
5241        settings.defaults.formatter = Some(language_settings::Formatter::Auto)
5242    });
5243
5244    let mut cx = EditorLspTestContext::new_rust(
5245        lsp::ServerCapabilities {
5246            document_formatting_provider: Some(lsp::OneOf::Left(true)),
5247            ..Default::default()
5248        },
5249        cx,
5250    )
5251    .await;
5252
5253    // Set up a buffer white some trailing whitespace and no trailing newline.
5254    cx.set_state(
5255        &[
5256            "one ",   //
5257            "twoˇ",   //
5258            "three ", //
5259            "four",   //
5260        ]
5261        .join("\n"),
5262    );
5263
5264    // Submit a format request.
5265    let format = cx
5266        .update_editor(|editor, cx| editor.format(&Format, cx))
5267        .unwrap();
5268
5269    // Record which buffer changes have been sent to the language server
5270    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
5271    cx.lsp
5272        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
5273            let buffer_changes = buffer_changes.clone();
5274            move |params, _| {
5275                buffer_changes.lock().extend(
5276                    params
5277                        .content_changes
5278                        .into_iter()
5279                        .map(|e| (e.range.unwrap(), e.text)),
5280                );
5281            }
5282        });
5283
5284    // Handle formatting requests to the language server.
5285    cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
5286        let buffer_changes = buffer_changes.clone();
5287        move |_, _| {
5288            // When formatting is requested, trailing whitespace has already been stripped,
5289            // and the trailing newline has already been added.
5290            assert_eq!(
5291                &buffer_changes.lock()[1..],
5292                &[
5293                    (
5294                        lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
5295                        "".into()
5296                    ),
5297                    (
5298                        lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
5299                        "".into()
5300                    ),
5301                    (
5302                        lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
5303                        "\n".into()
5304                    ),
5305                ]
5306            );
5307
5308            // Insert blank lines between each line of the buffer.
5309            async move {
5310                Ok(Some(vec![
5311                    lsp::TextEdit {
5312                        range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
5313                        new_text: "\n".into(),
5314                    },
5315                    lsp::TextEdit {
5316                        range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
5317                        new_text: "\n".into(),
5318                    },
5319                ]))
5320            }
5321        }
5322    });
5323
5324    // After formatting the buffer, the trailing whitespace is stripped,
5325    // a newline is appended, and the edits provided by the language server
5326    // have been applied.
5327    format.await.unwrap();
5328    cx.assert_editor_state(
5329        &[
5330            "one",   //
5331            "",      //
5332            "twoˇ",  //
5333            "",      //
5334            "three", //
5335            "four",  //
5336            "",      //
5337        ]
5338        .join("\n"),
5339    );
5340
5341    // Undoing the formatting undoes the trailing whitespace removal, the
5342    // trailing newline, and the LSP edits.
5343    cx.update_buffer(|buffer, cx| buffer.undo(cx));
5344    cx.assert_editor_state(
5345        &[
5346            "one ",   //
5347            "twoˇ",   //
5348            "three ", //
5349            "four",   //
5350        ]
5351        .join("\n"),
5352    );
5353}
5354
5355#[gpui::test]
5356async fn test_completion(cx: &mut gpui::TestAppContext) {
5357    init_test(cx, |_| {});
5358
5359    let mut cx = EditorLspTestContext::new_rust(
5360        lsp::ServerCapabilities {
5361            completion_provider: Some(lsp::CompletionOptions {
5362                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
5363                resolve_provider: Some(true),
5364                ..Default::default()
5365            }),
5366            ..Default::default()
5367        },
5368        cx,
5369    )
5370    .await;
5371
5372    cx.set_state(indoc! {"
5373        oneˇ
5374        two
5375        three
5376    "});
5377    cx.simulate_keystroke(".");
5378    handle_completion_request(
5379        &mut cx,
5380        indoc! {"
5381            one.|<>
5382            two
5383            three
5384        "},
5385        vec!["first_completion", "second_completion"],
5386    )
5387    .await;
5388    cx.condition(|editor, _| editor.context_menu_visible())
5389        .await;
5390    let apply_additional_edits = cx.update_editor(|editor, cx| {
5391        editor.context_menu_next(&Default::default(), cx);
5392        editor
5393            .confirm_completion(&ConfirmCompletion::default(), cx)
5394            .unwrap()
5395    });
5396    cx.assert_editor_state(indoc! {"
5397        one.second_completionˇ
5398        two
5399        three
5400    "});
5401
5402    handle_resolve_completion_request(
5403        &mut cx,
5404        Some(vec![
5405            (
5406                //This overlaps with the primary completion edit which is
5407                //misbehavior from the LSP spec, test that we filter it out
5408                indoc! {"
5409                    one.second_ˇcompletion
5410                    two
5411                    threeˇ
5412                "},
5413                "overlapping additional edit",
5414            ),
5415            (
5416                indoc! {"
5417                    one.second_completion
5418                    two
5419                    threeˇ
5420                "},
5421                "\nadditional edit",
5422            ),
5423        ]),
5424    )
5425    .await;
5426    apply_additional_edits.await.unwrap();
5427    cx.assert_editor_state(indoc! {"
5428        one.second_completionˇ
5429        two
5430        three
5431        additional edit
5432    "});
5433
5434    cx.set_state(indoc! {"
5435        one.second_completion
5436        twoˇ
5437        threeˇ
5438        additional edit
5439    "});
5440    cx.simulate_keystroke(" ");
5441    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5442    cx.simulate_keystroke("s");
5443    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5444
5445    cx.assert_editor_state(indoc! {"
5446        one.second_completion
5447        two sˇ
5448        three sˇ
5449        additional edit
5450    "});
5451    handle_completion_request(
5452        &mut cx,
5453        indoc! {"
5454            one.second_completion
5455            two s
5456            three <s|>
5457            additional edit
5458        "},
5459        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5460    )
5461    .await;
5462    cx.condition(|editor, _| editor.context_menu_visible())
5463        .await;
5464
5465    cx.simulate_keystroke("i");
5466
5467    handle_completion_request(
5468        &mut cx,
5469        indoc! {"
5470            one.second_completion
5471            two si
5472            three <si|>
5473            additional edit
5474        "},
5475        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5476    )
5477    .await;
5478    cx.condition(|editor, _| editor.context_menu_visible())
5479        .await;
5480
5481    let apply_additional_edits = cx.update_editor(|editor, cx| {
5482        editor
5483            .confirm_completion(&ConfirmCompletion::default(), cx)
5484            .unwrap()
5485    });
5486    cx.assert_editor_state(indoc! {"
5487        one.second_completion
5488        two sixth_completionˇ
5489        three sixth_completionˇ
5490        additional edit
5491    "});
5492
5493    handle_resolve_completion_request(&mut cx, None).await;
5494    apply_additional_edits.await.unwrap();
5495
5496    cx.update(|cx| {
5497        cx.update_global::<SettingsStore, _, _>(|settings, cx| {
5498            settings.update_user_settings::<EditorSettings>(cx, |settings| {
5499                settings.show_completions_on_input = Some(false);
5500            });
5501        })
5502    });
5503    cx.set_state("editorˇ");
5504    cx.simulate_keystroke(".");
5505    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5506    cx.simulate_keystroke("c");
5507    cx.simulate_keystroke("l");
5508    cx.simulate_keystroke("o");
5509    cx.assert_editor_state("editor.cloˇ");
5510    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5511    cx.update_editor(|editor, cx| {
5512        editor.show_completions(&ShowCompletions, cx);
5513    });
5514    handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
5515    cx.condition(|editor, _| editor.context_menu_visible())
5516        .await;
5517    let apply_additional_edits = cx.update_editor(|editor, cx| {
5518        editor
5519            .confirm_completion(&ConfirmCompletion::default(), cx)
5520            .unwrap()
5521    });
5522    cx.assert_editor_state("editor.closeˇ");
5523    handle_resolve_completion_request(&mut cx, None).await;
5524    apply_additional_edits.await.unwrap();
5525}
5526
5527#[gpui::test]
5528async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
5529    init_test(cx, |_| {});
5530    let mut cx = EditorTestContext::new(cx).await;
5531    let language = Arc::new(Language::new(
5532        LanguageConfig {
5533            line_comment: Some("// ".into()),
5534            ..Default::default()
5535        },
5536        Some(tree_sitter_rust::language()),
5537    ));
5538    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
5539
5540    // If multiple selections intersect a line, the line is only toggled once.
5541    cx.set_state(indoc! {"
5542        fn a() {
5543            «//b();
5544            ˇ»// «c();
5545            //ˇ»  d();
5546        }
5547    "});
5548
5549    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5550
5551    cx.assert_editor_state(indoc! {"
5552        fn a() {
5553            «b();
5554            c();
5555            ˇ» d();
5556        }
5557    "});
5558
5559    // The comment prefix is inserted at the same column for every line in a
5560    // selection.
5561    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5562
5563    cx.assert_editor_state(indoc! {"
5564        fn a() {
5565            // «b();
5566            // c();
5567            ˇ»//  d();
5568        }
5569    "});
5570
5571    // If a selection ends at the beginning of a line, that line is not toggled.
5572    cx.set_selections_state(indoc! {"
5573        fn a() {
5574            // b();
5575            «// c();
5576        ˇ»    //  d();
5577        }
5578    "});
5579
5580    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5581
5582    cx.assert_editor_state(indoc! {"
5583        fn a() {
5584            // b();
5585            «c();
5586        ˇ»    //  d();
5587        }
5588    "});
5589
5590    // If a selection span a single line and is empty, the line is toggled.
5591    cx.set_state(indoc! {"
5592        fn a() {
5593            a();
5594            b();
5595        ˇ
5596        }
5597    "});
5598
5599    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5600
5601    cx.assert_editor_state(indoc! {"
5602        fn a() {
5603            a();
5604            b();
5605        //•ˇ
5606        }
5607    "});
5608
5609    // If a selection span multiple lines, empty lines are not toggled.
5610    cx.set_state(indoc! {"
5611        fn a() {
5612            «a();
5613
5614            c();ˇ»
5615        }
5616    "});
5617
5618    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5619
5620    cx.assert_editor_state(indoc! {"
5621        fn a() {
5622            // «a();
5623
5624            // c();ˇ»
5625        }
5626    "});
5627}
5628
5629#[gpui::test]
5630async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
5631    init_test(cx, |_| {});
5632
5633    let language = Arc::new(Language::new(
5634        LanguageConfig {
5635            line_comment: Some("// ".into()),
5636            ..Default::default()
5637        },
5638        Some(tree_sitter_rust::language()),
5639    ));
5640
5641    let registry = Arc::new(LanguageRegistry::test());
5642    registry.add(language.clone());
5643
5644    let mut cx = EditorTestContext::new(cx).await;
5645    cx.update_buffer(|buffer, cx| {
5646        buffer.set_language_registry(registry);
5647        buffer.set_language(Some(language), cx);
5648    });
5649
5650    let toggle_comments = &ToggleComments {
5651        advance_downwards: true,
5652    };
5653
5654    // Single cursor on one line -> advance
5655    // Cursor moves horizontally 3 characters as well on non-blank line
5656    cx.set_state(indoc!(
5657        "fn a() {
5658             ˇdog();
5659             cat();
5660        }"
5661    ));
5662    cx.update_editor(|editor, cx| {
5663        editor.toggle_comments(toggle_comments, cx);
5664    });
5665    cx.assert_editor_state(indoc!(
5666        "fn a() {
5667             // dog();
5668             catˇ();
5669        }"
5670    ));
5671
5672    // Single selection on one line -> don't advance
5673    cx.set_state(indoc!(
5674        "fn a() {
5675             «dog()ˇ»;
5676             cat();
5677        }"
5678    ));
5679    cx.update_editor(|editor, cx| {
5680        editor.toggle_comments(toggle_comments, cx);
5681    });
5682    cx.assert_editor_state(indoc!(
5683        "fn a() {
5684             // «dog()ˇ»;
5685             cat();
5686        }"
5687    ));
5688
5689    // Multiple cursors on one line -> advance
5690    cx.set_state(indoc!(
5691        "fn a() {
5692             ˇdˇog();
5693             cat();
5694        }"
5695    ));
5696    cx.update_editor(|editor, cx| {
5697        editor.toggle_comments(toggle_comments, cx);
5698    });
5699    cx.assert_editor_state(indoc!(
5700        "fn a() {
5701             // dog();
5702             catˇ(ˇ);
5703        }"
5704    ));
5705
5706    // Multiple cursors on one line, with selection -> don't advance
5707    cx.set_state(indoc!(
5708        "fn a() {
5709             ˇdˇog«()ˇ»;
5710             cat();
5711        }"
5712    ));
5713    cx.update_editor(|editor, cx| {
5714        editor.toggle_comments(toggle_comments, cx);
5715    });
5716    cx.assert_editor_state(indoc!(
5717        "fn a() {
5718             // ˇdˇog«()ˇ»;
5719             cat();
5720        }"
5721    ));
5722
5723    // Single cursor on one line -> advance
5724    // Cursor moves to column 0 on blank line
5725    cx.set_state(indoc!(
5726        "fn a() {
5727             ˇdog();
5728
5729             cat();
5730        }"
5731    ));
5732    cx.update_editor(|editor, cx| {
5733        editor.toggle_comments(toggle_comments, cx);
5734    });
5735    cx.assert_editor_state(indoc!(
5736        "fn a() {
5737             // dog();
5738        ˇ
5739             cat();
5740        }"
5741    ));
5742
5743    // Single cursor on one line -> advance
5744    // Cursor starts and ends at column 0
5745    cx.set_state(indoc!(
5746        "fn a() {
5747         ˇ    dog();
5748             cat();
5749        }"
5750    ));
5751    cx.update_editor(|editor, cx| {
5752        editor.toggle_comments(toggle_comments, cx);
5753    });
5754    cx.assert_editor_state(indoc!(
5755        "fn a() {
5756             // dog();
5757         ˇ    cat();
5758        }"
5759    ));
5760}
5761
5762#[gpui::test]
5763async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
5764    init_test(cx, |_| {});
5765
5766    let mut cx = EditorTestContext::new(cx).await;
5767
5768    let html_language = Arc::new(
5769        Language::new(
5770            LanguageConfig {
5771                name: "HTML".into(),
5772                block_comment: Some(("<!-- ".into(), " -->".into())),
5773                ..Default::default()
5774            },
5775            Some(tree_sitter_html::language()),
5776        )
5777        .with_injection_query(
5778            r#"
5779            (script_element
5780                (raw_text) @content
5781                (#set! "language" "javascript"))
5782            "#,
5783        )
5784        .unwrap(),
5785    );
5786
5787    let javascript_language = Arc::new(Language::new(
5788        LanguageConfig {
5789            name: "JavaScript".into(),
5790            line_comment: Some("// ".into()),
5791            ..Default::default()
5792        },
5793        Some(tree_sitter_typescript::language_tsx()),
5794    ));
5795
5796    let registry = Arc::new(LanguageRegistry::test());
5797    registry.add(html_language.clone());
5798    registry.add(javascript_language.clone());
5799
5800    cx.update_buffer(|buffer, cx| {
5801        buffer.set_language_registry(registry);
5802        buffer.set_language(Some(html_language), cx);
5803    });
5804
5805    // Toggle comments for empty selections
5806    cx.set_state(
5807        &r#"
5808            <p>A</p>ˇ
5809            <p>B</p>ˇ
5810            <p>C</p>ˇ
5811        "#
5812        .unindent(),
5813    );
5814    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5815    cx.assert_editor_state(
5816        &r#"
5817            <!-- <p>A</p>ˇ -->
5818            <!-- <p>B</p>ˇ -->
5819            <!-- <p>C</p>ˇ -->
5820        "#
5821        .unindent(),
5822    );
5823    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5824    cx.assert_editor_state(
5825        &r#"
5826            <p>A</p>ˇ
5827            <p>B</p>ˇ
5828            <p>C</p>ˇ
5829        "#
5830        .unindent(),
5831    );
5832
5833    // Toggle comments for mixture of empty and non-empty selections, where
5834    // multiple selections occupy a given line.
5835    cx.set_state(
5836        &r#"
5837            <p>A«</p>
5838            <p>ˇ»B</p>ˇ
5839            <p>C«</p>
5840            <p>ˇ»D</p>ˇ
5841        "#
5842        .unindent(),
5843    );
5844
5845    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5846    cx.assert_editor_state(
5847        &r#"
5848            <!-- <p>A«</p>
5849            <p>ˇ»B</p>ˇ -->
5850            <!-- <p>C«</p>
5851            <p>ˇ»D</p>ˇ -->
5852        "#
5853        .unindent(),
5854    );
5855    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5856    cx.assert_editor_state(
5857        &r#"
5858            <p>A«</p>
5859            <p>ˇ»B</p>ˇ
5860            <p>C«</p>
5861            <p>ˇ»D</p>ˇ
5862        "#
5863        .unindent(),
5864    );
5865
5866    // Toggle comments when different languages are active for different
5867    // selections.
5868    cx.set_state(
5869        &r#"
5870            ˇ<script>
5871                ˇvar x = new Y();
5872            ˇ</script>
5873        "#
5874        .unindent(),
5875    );
5876    cx.foreground().run_until_parked();
5877    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5878    cx.assert_editor_state(
5879        &r#"
5880            <!-- ˇ<script> -->
5881                // ˇvar x = new Y();
5882            <!-- ˇ</script> -->
5883        "#
5884        .unindent(),
5885    );
5886}
5887
5888#[gpui::test]
5889fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
5890    init_test(cx, |_| {});
5891
5892    let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, sample_text(3, 4, 'a')));
5893    let multibuffer = cx.add_model(|cx| {
5894        let mut multibuffer = MultiBuffer::new(0);
5895        multibuffer.push_excerpts(
5896            buffer.clone(),
5897            [
5898                ExcerptRange {
5899                    context: Point::new(0, 0)..Point::new(0, 4),
5900                    primary: None,
5901                },
5902                ExcerptRange {
5903                    context: Point::new(1, 0)..Point::new(1, 4),
5904                    primary: None,
5905                },
5906            ],
5907            cx,
5908        );
5909        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
5910        multibuffer
5911    });
5912
5913    let view = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
5914    view.update(cx, |view, cx| {
5915        assert_eq!(view.text(cx), "aaaa\nbbbb");
5916        view.change_selections(None, cx, |s| {
5917            s.select_ranges([
5918                Point::new(0, 0)..Point::new(0, 0),
5919                Point::new(1, 0)..Point::new(1, 0),
5920            ])
5921        });
5922
5923        view.handle_input("X", cx);
5924        assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
5925        assert_eq!(
5926            view.selections.ranges(cx),
5927            [
5928                Point::new(0, 1)..Point::new(0, 1),
5929                Point::new(1, 1)..Point::new(1, 1),
5930            ]
5931        );
5932
5933        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
5934        view.change_selections(None, cx, |s| {
5935            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
5936        });
5937        view.backspace(&Default::default(), cx);
5938        assert_eq!(view.text(cx), "Xa\nbbb");
5939        assert_eq!(
5940            view.selections.ranges(cx),
5941            [Point::new(1, 0)..Point::new(1, 0)]
5942        );
5943
5944        view.change_selections(None, cx, |s| {
5945            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
5946        });
5947        view.backspace(&Default::default(), cx);
5948        assert_eq!(view.text(cx), "X\nbb");
5949        assert_eq!(
5950            view.selections.ranges(cx),
5951            [Point::new(0, 1)..Point::new(0, 1)]
5952        );
5953    });
5954}
5955
5956#[gpui::test]
5957fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
5958    init_test(cx, |_| {});
5959
5960    let markers = vec![('[', ']').into(), ('(', ')').into()];
5961    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
5962        indoc! {"
5963            [aaaa
5964            (bbbb]
5965            cccc)",
5966        },
5967        markers.clone(),
5968    );
5969    let excerpt_ranges = markers.into_iter().map(|marker| {
5970        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
5971        ExcerptRange {
5972            context,
5973            primary: None,
5974        }
5975    });
5976    let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, initial_text));
5977    let multibuffer = cx.add_model(|cx| {
5978        let mut multibuffer = MultiBuffer::new(0);
5979        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
5980        multibuffer
5981    });
5982
5983    let view = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
5984    view.update(cx, |view, cx| {
5985        let (expected_text, selection_ranges) = marked_text_ranges(
5986            indoc! {"
5987                aaaa
5988                bˇbbb
5989                bˇbbˇb
5990                cccc"
5991            },
5992            true,
5993        );
5994        assert_eq!(view.text(cx), expected_text);
5995        view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
5996
5997        view.handle_input("X", cx);
5998
5999        let (expected_text, expected_selections) = marked_text_ranges(
6000            indoc! {"
6001                aaaa
6002                bXˇbbXb
6003                bXˇbbXˇb
6004                cccc"
6005            },
6006            false,
6007        );
6008        assert_eq!(view.text(cx), expected_text);
6009        assert_eq!(view.selections.ranges(cx), expected_selections);
6010
6011        view.newline(&Newline, cx);
6012        let (expected_text, expected_selections) = marked_text_ranges(
6013            indoc! {"
6014                aaaa
6015                bX
6016                ˇbbX
6017                b
6018                bX
6019                ˇbbX
6020                ˇb
6021                cccc"
6022            },
6023            false,
6024        );
6025        assert_eq!(view.text(cx), expected_text);
6026        assert_eq!(view.selections.ranges(cx), expected_selections);
6027    });
6028}
6029
6030#[gpui::test]
6031fn test_refresh_selections(cx: &mut TestAppContext) {
6032    init_test(cx, |_| {});
6033
6034    let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, sample_text(3, 4, 'a')));
6035    let mut excerpt1_id = None;
6036    let multibuffer = cx.add_model(|cx| {
6037        let mut multibuffer = MultiBuffer::new(0);
6038        excerpt1_id = multibuffer
6039            .push_excerpts(
6040                buffer.clone(),
6041                [
6042                    ExcerptRange {
6043                        context: Point::new(0, 0)..Point::new(1, 4),
6044                        primary: None,
6045                    },
6046                    ExcerptRange {
6047                        context: Point::new(1, 0)..Point::new(2, 4),
6048                        primary: None,
6049                    },
6050                ],
6051                cx,
6052            )
6053            .into_iter()
6054            .next();
6055        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6056        multibuffer
6057    });
6058
6059    let editor = cx
6060        .add_window(|cx| {
6061            let mut editor = build_editor(multibuffer.clone(), cx);
6062            let snapshot = editor.snapshot(cx);
6063            editor.change_selections(None, cx, |s| {
6064                s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
6065            });
6066            editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
6067            assert_eq!(
6068                editor.selections.ranges(cx),
6069                [
6070                    Point::new(1, 3)..Point::new(1, 3),
6071                    Point::new(2, 1)..Point::new(2, 1),
6072                ]
6073            );
6074            editor
6075        })
6076        .root(cx);
6077
6078    // Refreshing selections is a no-op when excerpts haven't changed.
6079    editor.update(cx, |editor, cx| {
6080        editor.change_selections(None, cx, |s| s.refresh());
6081        assert_eq!(
6082            editor.selections.ranges(cx),
6083            [
6084                Point::new(1, 3)..Point::new(1, 3),
6085                Point::new(2, 1)..Point::new(2, 1),
6086            ]
6087        );
6088    });
6089
6090    multibuffer.update(cx, |multibuffer, cx| {
6091        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6092    });
6093    editor.update(cx, |editor, cx| {
6094        // Removing an excerpt causes the first selection to become degenerate.
6095        assert_eq!(
6096            editor.selections.ranges(cx),
6097            [
6098                Point::new(0, 0)..Point::new(0, 0),
6099                Point::new(0, 1)..Point::new(0, 1)
6100            ]
6101        );
6102
6103        // Refreshing selections will relocate the first selection to the original buffer
6104        // location.
6105        editor.change_selections(None, cx, |s| s.refresh());
6106        assert_eq!(
6107            editor.selections.ranges(cx),
6108            [
6109                Point::new(0, 1)..Point::new(0, 1),
6110                Point::new(0, 3)..Point::new(0, 3)
6111            ]
6112        );
6113        assert!(editor.selections.pending_anchor().is_some());
6114    });
6115}
6116
6117#[gpui::test]
6118fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
6119    init_test(cx, |_| {});
6120
6121    let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, sample_text(3, 4, 'a')));
6122    let mut excerpt1_id = None;
6123    let multibuffer = cx.add_model(|cx| {
6124        let mut multibuffer = MultiBuffer::new(0);
6125        excerpt1_id = multibuffer
6126            .push_excerpts(
6127                buffer.clone(),
6128                [
6129                    ExcerptRange {
6130                        context: Point::new(0, 0)..Point::new(1, 4),
6131                        primary: None,
6132                    },
6133                    ExcerptRange {
6134                        context: Point::new(1, 0)..Point::new(2, 4),
6135                        primary: None,
6136                    },
6137                ],
6138                cx,
6139            )
6140            .into_iter()
6141            .next();
6142        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6143        multibuffer
6144    });
6145
6146    let editor = cx
6147        .add_window(|cx| {
6148            let mut editor = build_editor(multibuffer.clone(), cx);
6149            let snapshot = editor.snapshot(cx);
6150            editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
6151            assert_eq!(
6152                editor.selections.ranges(cx),
6153                [Point::new(1, 3)..Point::new(1, 3)]
6154            );
6155            editor
6156        })
6157        .root(cx);
6158
6159    multibuffer.update(cx, |multibuffer, cx| {
6160        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6161    });
6162    editor.update(cx, |editor, cx| {
6163        assert_eq!(
6164            editor.selections.ranges(cx),
6165            [Point::new(0, 0)..Point::new(0, 0)]
6166        );
6167
6168        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
6169        editor.change_selections(None, cx, |s| s.refresh());
6170        assert_eq!(
6171            editor.selections.ranges(cx),
6172            [Point::new(0, 3)..Point::new(0, 3)]
6173        );
6174        assert!(editor.selections.pending_anchor().is_some());
6175    });
6176}
6177
6178#[gpui::test]
6179async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
6180    init_test(cx, |_| {});
6181
6182    let language = Arc::new(
6183        Language::new(
6184            LanguageConfig {
6185                brackets: BracketPairConfig {
6186                    pairs: vec![
6187                        BracketPair {
6188                            start: "{".to_string(),
6189                            end: "}".to_string(),
6190                            close: true,
6191                            newline: true,
6192                        },
6193                        BracketPair {
6194                            start: "/* ".to_string(),
6195                            end: " */".to_string(),
6196                            close: true,
6197                            newline: true,
6198                        },
6199                    ],
6200                    ..Default::default()
6201                },
6202                ..Default::default()
6203            },
6204            Some(tree_sitter_rust::language()),
6205        )
6206        .with_indents_query("")
6207        .unwrap(),
6208    );
6209
6210    let text = concat!(
6211        "{   }\n",     //
6212        "  x\n",       //
6213        "  /*   */\n", //
6214        "x\n",         //
6215        "{{} }\n",     //
6216    );
6217
6218    let buffer =
6219        cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text).with_language(language, cx));
6220    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
6221    let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
6222    view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6223        .await;
6224
6225    view.update(cx, |view, cx| {
6226        view.change_selections(None, cx, |s| {
6227            s.select_display_ranges([
6228                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
6229                DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
6230                DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
6231            ])
6232        });
6233        view.newline(&Newline, cx);
6234
6235        assert_eq!(
6236            view.buffer().read(cx).read(cx).text(),
6237            concat!(
6238                "{ \n",    // Suppress rustfmt
6239                "\n",      //
6240                "}\n",     //
6241                "  x\n",   //
6242                "  /* \n", //
6243                "  \n",    //
6244                "  */\n",  //
6245                "x\n",     //
6246                "{{} \n",  //
6247                "}\n",     //
6248            )
6249        );
6250    });
6251}
6252
6253#[gpui::test]
6254fn test_highlighted_ranges(cx: &mut TestAppContext) {
6255    init_test(cx, |_| {});
6256
6257    let editor = cx
6258        .add_window(|cx| {
6259            let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
6260            build_editor(buffer.clone(), cx)
6261        })
6262        .root(cx);
6263
6264    editor.update(cx, |editor, cx| {
6265        struct Type1;
6266        struct Type2;
6267
6268        let buffer = editor.buffer.read(cx).snapshot(cx);
6269
6270        let anchor_range =
6271            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
6272
6273        editor.highlight_background::<Type1>(
6274            vec![
6275                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
6276                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
6277                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
6278                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
6279            ],
6280            |_| Color::red(),
6281            cx,
6282        );
6283        editor.highlight_background::<Type2>(
6284            vec![
6285                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
6286                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
6287                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
6288                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
6289            ],
6290            |_| Color::green(),
6291            cx,
6292        );
6293
6294        let snapshot = editor.snapshot(cx);
6295        let mut highlighted_ranges = editor.background_highlights_in_range(
6296            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
6297            &snapshot,
6298            theme::current(cx).as_ref(),
6299        );
6300        // Enforce a consistent ordering based on color without relying on the ordering of the
6301        // highlight's `TypeId` which is non-deterministic.
6302        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
6303        assert_eq!(
6304            highlighted_ranges,
6305            &[
6306                (
6307                    DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
6308                    Color::green(),
6309                ),
6310                (
6311                    DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
6312                    Color::green(),
6313                ),
6314                (
6315                    DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
6316                    Color::red(),
6317                ),
6318                (
6319                    DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6320                    Color::red(),
6321                ),
6322            ]
6323        );
6324        assert_eq!(
6325            editor.background_highlights_in_range(
6326                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
6327                &snapshot,
6328                theme::current(cx).as_ref(),
6329            ),
6330            &[(
6331                DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6332                Color::red(),
6333            )]
6334        );
6335    });
6336}
6337
6338#[gpui::test]
6339async fn test_following(cx: &mut gpui::TestAppContext) {
6340    init_test(cx, |_| {});
6341
6342    let fs = FakeFs::new(cx.background());
6343    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6344
6345    let buffer = project.update(cx, |project, cx| {
6346        let buffer = project
6347            .create_buffer(&sample_text(16, 8, 'a'), None, cx)
6348            .unwrap();
6349        cx.add_model(|cx| MultiBuffer::singleton(buffer, cx))
6350    });
6351    let leader = cx
6352        .add_window(|cx| build_editor(buffer.clone(), cx))
6353        .root(cx);
6354    let follower = cx
6355        .update(|cx| {
6356            cx.add_window(
6357                WindowOptions {
6358                    bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
6359                    ..Default::default()
6360                },
6361                |cx| build_editor(buffer.clone(), cx),
6362            )
6363        })
6364        .root(cx);
6365
6366    let is_still_following = Rc::new(RefCell::new(true));
6367    let follower_edit_event_count = Rc::new(RefCell::new(0));
6368    let pending_update = Rc::new(RefCell::new(None));
6369    follower.update(cx, {
6370        let update = pending_update.clone();
6371        let is_still_following = is_still_following.clone();
6372        let follower_edit_event_count = follower_edit_event_count.clone();
6373        |_, cx| {
6374            cx.subscribe(&leader, move |_, leader, event, cx| {
6375                leader
6376                    .read(cx)
6377                    .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6378            })
6379            .detach();
6380
6381            cx.subscribe(&follower, move |_, _, event, cx| {
6382                if Editor::should_unfollow_on_event(event, cx) {
6383                    *is_still_following.borrow_mut() = false;
6384                }
6385                if let Event::BufferEdited = event {
6386                    *follower_edit_event_count.borrow_mut() += 1;
6387                }
6388            })
6389            .detach();
6390        }
6391    });
6392
6393    // Update the selections only
6394    leader.update(cx, |leader, cx| {
6395        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6396    });
6397    follower
6398        .update(cx, |follower, cx| {
6399            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6400        })
6401        .await
6402        .unwrap();
6403    follower.read_with(cx, |follower, cx| {
6404        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
6405    });
6406    assert_eq!(*is_still_following.borrow(), true);
6407    assert_eq!(*follower_edit_event_count.borrow(), 0);
6408
6409    // Update the scroll position only
6410    leader.update(cx, |leader, cx| {
6411        leader.set_scroll_position(vec2f(1.5, 3.5), cx);
6412    });
6413    follower
6414        .update(cx, |follower, cx| {
6415            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6416        })
6417        .await
6418        .unwrap();
6419    assert_eq!(
6420        follower.update(cx, |follower, cx| follower.scroll_position(cx)),
6421        vec2f(1.5, 3.5)
6422    );
6423    assert_eq!(*is_still_following.borrow(), true);
6424    assert_eq!(*follower_edit_event_count.borrow(), 0);
6425
6426    // Update the selections and scroll position. The follower's scroll position is updated
6427    // via autoscroll, not via the leader's exact scroll position.
6428    leader.update(cx, |leader, cx| {
6429        leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
6430        leader.request_autoscroll(Autoscroll::newest(), cx);
6431        leader.set_scroll_position(vec2f(1.5, 3.5), cx);
6432    });
6433    follower
6434        .update(cx, |follower, cx| {
6435            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6436        })
6437        .await
6438        .unwrap();
6439    follower.update(cx, |follower, cx| {
6440        assert_eq!(follower.scroll_position(cx), vec2f(1.5, 0.0));
6441        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
6442    });
6443    assert_eq!(*is_still_following.borrow(), true);
6444
6445    // Creating a pending selection that precedes another selection
6446    leader.update(cx, |leader, cx| {
6447        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6448        leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
6449    });
6450    follower
6451        .update(cx, |follower, cx| {
6452            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6453        })
6454        .await
6455        .unwrap();
6456    follower.read_with(cx, |follower, cx| {
6457        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
6458    });
6459    assert_eq!(*is_still_following.borrow(), true);
6460
6461    // Extend the pending selection so that it surrounds another selection
6462    leader.update(cx, |leader, cx| {
6463        leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
6464    });
6465    follower
6466        .update(cx, |follower, cx| {
6467            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6468        })
6469        .await
6470        .unwrap();
6471    follower.read_with(cx, |follower, cx| {
6472        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
6473    });
6474
6475    // Scrolling locally breaks the follow
6476    follower.update(cx, |follower, cx| {
6477        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
6478        follower.set_scroll_anchor(
6479            ScrollAnchor {
6480                anchor: top_anchor,
6481                offset: vec2f(0.0, 0.5),
6482            },
6483            cx,
6484        );
6485    });
6486    assert_eq!(*is_still_following.borrow(), false);
6487}
6488
6489#[gpui::test]
6490async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
6491    init_test(cx, |_| {});
6492
6493    let fs = FakeFs::new(cx.background());
6494    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6495    let workspace = cx
6496        .add_window(|cx| Workspace::test_new(project.clone(), cx))
6497        .root(cx);
6498    let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
6499
6500    let leader = pane.update(cx, |_, cx| {
6501        let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
6502        cx.add_view(|cx| build_editor(multibuffer.clone(), cx))
6503    });
6504
6505    // Start following the editor when it has no excerpts.
6506    let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6507    let follower_1 = cx
6508        .update(|cx| {
6509            Editor::from_state_proto(
6510                pane.clone(),
6511                workspace.clone(),
6512                ViewId {
6513                    creator: Default::default(),
6514                    id: 0,
6515                },
6516                &mut state_message,
6517                cx,
6518            )
6519        })
6520        .unwrap()
6521        .await
6522        .unwrap();
6523
6524    let update_message = Rc::new(RefCell::new(None));
6525    follower_1.update(cx, {
6526        let update = update_message.clone();
6527        |_, cx| {
6528            cx.subscribe(&leader, move |_, leader, event, cx| {
6529                leader
6530                    .read(cx)
6531                    .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6532            })
6533            .detach();
6534        }
6535    });
6536
6537    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
6538        (
6539            project
6540                .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
6541                .unwrap(),
6542            project
6543                .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
6544                .unwrap(),
6545        )
6546    });
6547
6548    // Insert some excerpts.
6549    leader.update(cx, |leader, cx| {
6550        leader.buffer.update(cx, |multibuffer, cx| {
6551            let excerpt_ids = multibuffer.push_excerpts(
6552                buffer_1.clone(),
6553                [
6554                    ExcerptRange {
6555                        context: 1..6,
6556                        primary: None,
6557                    },
6558                    ExcerptRange {
6559                        context: 12..15,
6560                        primary: None,
6561                    },
6562                    ExcerptRange {
6563                        context: 0..3,
6564                        primary: None,
6565                    },
6566                ],
6567                cx,
6568            );
6569            multibuffer.insert_excerpts_after(
6570                excerpt_ids[0],
6571                buffer_2.clone(),
6572                [
6573                    ExcerptRange {
6574                        context: 8..12,
6575                        primary: None,
6576                    },
6577                    ExcerptRange {
6578                        context: 0..6,
6579                        primary: None,
6580                    },
6581                ],
6582                cx,
6583            );
6584        });
6585    });
6586
6587    // Apply the update of adding the excerpts.
6588    follower_1
6589        .update(cx, |follower, cx| {
6590            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6591        })
6592        .await
6593        .unwrap();
6594    assert_eq!(
6595        follower_1.read_with(cx, |editor, cx| editor.text(cx)),
6596        leader.read_with(cx, |editor, cx| editor.text(cx))
6597    );
6598    update_message.borrow_mut().take();
6599
6600    // Start following separately after it already has excerpts.
6601    let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6602    let follower_2 = cx
6603        .update(|cx| {
6604            Editor::from_state_proto(
6605                pane.clone(),
6606                workspace.clone(),
6607                ViewId {
6608                    creator: Default::default(),
6609                    id: 0,
6610                },
6611                &mut state_message,
6612                cx,
6613            )
6614        })
6615        .unwrap()
6616        .await
6617        .unwrap();
6618    assert_eq!(
6619        follower_2.read_with(cx, |editor, cx| editor.text(cx)),
6620        leader.read_with(cx, |editor, cx| editor.text(cx))
6621    );
6622
6623    // Remove some excerpts.
6624    leader.update(cx, |leader, cx| {
6625        leader.buffer.update(cx, |multibuffer, cx| {
6626            let excerpt_ids = multibuffer.excerpt_ids();
6627            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
6628            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
6629        });
6630    });
6631
6632    // Apply the update of removing the excerpts.
6633    follower_1
6634        .update(cx, |follower, cx| {
6635            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6636        })
6637        .await
6638        .unwrap();
6639    follower_2
6640        .update(cx, |follower, cx| {
6641            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6642        })
6643        .await
6644        .unwrap();
6645    update_message.borrow_mut().take();
6646    assert_eq!(
6647        follower_1.read_with(cx, |editor, cx| editor.text(cx)),
6648        leader.read_with(cx, |editor, cx| editor.text(cx))
6649    );
6650}
6651
6652#[test]
6653fn test_combine_syntax_and_fuzzy_match_highlights() {
6654    let string = "abcdefghijklmnop";
6655    let syntax_ranges = [
6656        (
6657            0..3,
6658            HighlightStyle {
6659                color: Some(Color::red()),
6660                ..Default::default()
6661            },
6662        ),
6663        (
6664            4..8,
6665            HighlightStyle {
6666                color: Some(Color::green()),
6667                ..Default::default()
6668            },
6669        ),
6670    ];
6671    let match_indices = [4, 6, 7, 8];
6672    assert_eq!(
6673        combine_syntax_and_fuzzy_match_highlights(
6674            string,
6675            Default::default(),
6676            syntax_ranges.into_iter(),
6677            &match_indices,
6678        ),
6679        &[
6680            (
6681                0..3,
6682                HighlightStyle {
6683                    color: Some(Color::red()),
6684                    ..Default::default()
6685                },
6686            ),
6687            (
6688                4..5,
6689                HighlightStyle {
6690                    color: Some(Color::green()),
6691                    weight: Some(fonts::Weight::BOLD),
6692                    ..Default::default()
6693                },
6694            ),
6695            (
6696                5..6,
6697                HighlightStyle {
6698                    color: Some(Color::green()),
6699                    ..Default::default()
6700                },
6701            ),
6702            (
6703                6..8,
6704                HighlightStyle {
6705                    color: Some(Color::green()),
6706                    weight: Some(fonts::Weight::BOLD),
6707                    ..Default::default()
6708                },
6709            ),
6710            (
6711                8..9,
6712                HighlightStyle {
6713                    weight: Some(fonts::Weight::BOLD),
6714                    ..Default::default()
6715                },
6716            ),
6717        ]
6718    );
6719}
6720
6721#[gpui::test]
6722async fn go_to_hunk(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
6723    init_test(cx, |_| {});
6724
6725    let mut cx = EditorTestContext::new(cx).await;
6726
6727    let diff_base = r#"
6728        use some::mod;
6729
6730        const A: u32 = 42;
6731
6732        fn main() {
6733            println!("hello");
6734
6735            println!("world");
6736        }
6737        "#
6738    .unindent();
6739
6740    // Edits are modified, removed, modified, added
6741    cx.set_state(
6742        &r#"
6743        use some::modified;
6744
6745        ˇ
6746        fn main() {
6747            println!("hello there");
6748
6749            println!("around the");
6750            println!("world");
6751        }
6752        "#
6753        .unindent(),
6754    );
6755
6756    cx.set_diff_base(Some(&diff_base));
6757    deterministic.run_until_parked();
6758
6759    cx.update_editor(|editor, cx| {
6760        //Wrap around the bottom of the buffer
6761        for _ in 0..3 {
6762            editor.go_to_hunk(&GoToHunk, cx);
6763        }
6764    });
6765
6766    cx.assert_editor_state(
6767        &r#"
6768        ˇuse some::modified;
6769
6770
6771        fn main() {
6772            println!("hello there");
6773
6774            println!("around the");
6775            println!("world");
6776        }
6777        "#
6778        .unindent(),
6779    );
6780
6781    cx.update_editor(|editor, cx| {
6782        //Wrap around the top of the buffer
6783        for _ in 0..2 {
6784            editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6785        }
6786    });
6787
6788    cx.assert_editor_state(
6789        &r#"
6790        use some::modified;
6791
6792
6793        fn main() {
6794        ˇ    println!("hello there");
6795
6796            println!("around the");
6797            println!("world");
6798        }
6799        "#
6800        .unindent(),
6801    );
6802
6803    cx.update_editor(|editor, cx| {
6804        editor.fold(&Fold, cx);
6805
6806        //Make sure that the fold only gets one hunk
6807        for _ in 0..4 {
6808            editor.go_to_hunk(&GoToHunk, cx);
6809        }
6810    });
6811
6812    cx.assert_editor_state(
6813        &r#"
6814        ˇuse some::modified;
6815
6816
6817        fn main() {
6818            println!("hello there");
6819
6820            println!("around the");
6821            println!("world");
6822        }
6823        "#
6824        .unindent(),
6825    );
6826}
6827
6828#[test]
6829fn test_split_words() {
6830    fn split<'a>(text: &'a str) -> Vec<&'a str> {
6831        split_words(text).collect()
6832    }
6833
6834    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
6835    assert_eq!(split("hello_world"), &["hello_", "world"]);
6836    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
6837    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
6838    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
6839    assert_eq!(split("helloworld"), &["helloworld"]);
6840}
6841
6842#[gpui::test]
6843async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
6844    init_test(cx, |_| {});
6845
6846    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
6847    let mut assert = |before, after| {
6848        let _state_context = cx.set_state(before);
6849        cx.update_editor(|editor, cx| {
6850            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
6851        });
6852        cx.assert_editor_state(after);
6853    };
6854
6855    // Outside bracket jumps to outside of matching bracket
6856    assert("console.logˇ(var);", "console.log(var)ˇ;");
6857    assert("console.log(var)ˇ;", "console.logˇ(var);");
6858
6859    // Inside bracket jumps to inside of matching bracket
6860    assert("console.log(ˇvar);", "console.log(varˇ);");
6861    assert("console.log(varˇ);", "console.log(ˇvar);");
6862
6863    // When outside a bracket and inside, favor jumping to the inside bracket
6864    assert(
6865        "console.log('foo', [1, 2, 3]ˇ);",
6866        "console.log(ˇ'foo', [1, 2, 3]);",
6867    );
6868    assert(
6869        "console.log(ˇ'foo', [1, 2, 3]);",
6870        "console.log('foo', [1, 2, 3]ˇ);",
6871    );
6872
6873    // Bias forward if two options are equally likely
6874    assert(
6875        "let result = curried_fun()ˇ();",
6876        "let result = curried_fun()()ˇ;",
6877    );
6878
6879    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
6880    assert(
6881        indoc! {"
6882            function test() {
6883                console.log('test')ˇ
6884            }"},
6885        indoc! {"
6886            function test() {
6887                console.logˇ('test')
6888            }"},
6889    );
6890}
6891
6892#[gpui::test(iterations = 10)]
6893async fn test_copilot(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
6894    init_test(cx, |_| {});
6895
6896    let (copilot, copilot_lsp) = Copilot::fake(cx);
6897    cx.update(|cx| cx.set_global(copilot));
6898    let mut cx = EditorLspTestContext::new_rust(
6899        lsp::ServerCapabilities {
6900            completion_provider: Some(lsp::CompletionOptions {
6901                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
6902                ..Default::default()
6903            }),
6904            ..Default::default()
6905        },
6906        cx,
6907    )
6908    .await;
6909
6910    // When inserting, ensure autocompletion is favored over Copilot suggestions.
6911    cx.set_state(indoc! {"
6912        oneˇ
6913        two
6914        three
6915    "});
6916    cx.simulate_keystroke(".");
6917    let _ = handle_completion_request(
6918        &mut cx,
6919        indoc! {"
6920            one.|<>
6921            two
6922            three
6923        "},
6924        vec!["completion_a", "completion_b"],
6925    );
6926    handle_copilot_completion_request(
6927        &copilot_lsp,
6928        vec![copilot::request::Completion {
6929            text: "one.copilot1".into(),
6930            range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6931            ..Default::default()
6932        }],
6933        vec![],
6934    );
6935    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6936    cx.update_editor(|editor, cx| {
6937        assert!(editor.context_menu_visible());
6938        assert!(!editor.has_active_copilot_suggestion(cx));
6939
6940        // Confirming a completion inserts it and hides the context menu, without showing
6941        // the copilot suggestion afterwards.
6942        editor
6943            .confirm_completion(&Default::default(), cx)
6944            .unwrap()
6945            .detach();
6946        assert!(!editor.context_menu_visible());
6947        assert!(!editor.has_active_copilot_suggestion(cx));
6948        assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
6949        assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
6950    });
6951
6952    // Ensure Copilot suggestions are shown right away if no autocompletion is available.
6953    cx.set_state(indoc! {"
6954        oneˇ
6955        two
6956        three
6957    "});
6958    cx.simulate_keystroke(".");
6959    let _ = handle_completion_request(
6960        &mut cx,
6961        indoc! {"
6962            one.|<>
6963            two
6964            three
6965        "},
6966        vec![],
6967    );
6968    handle_copilot_completion_request(
6969        &copilot_lsp,
6970        vec![copilot::request::Completion {
6971            text: "one.copilot1".into(),
6972            range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6973            ..Default::default()
6974        }],
6975        vec![],
6976    );
6977    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6978    cx.update_editor(|editor, cx| {
6979        assert!(!editor.context_menu_visible());
6980        assert!(editor.has_active_copilot_suggestion(cx));
6981        assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6982        assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
6983    });
6984
6985    // Reset editor, and ensure autocompletion is still favored over Copilot suggestions.
6986    cx.set_state(indoc! {"
6987        oneˇ
6988        two
6989        three
6990    "});
6991    cx.simulate_keystroke(".");
6992    let _ = handle_completion_request(
6993        &mut cx,
6994        indoc! {"
6995            one.|<>
6996            two
6997            three
6998        "},
6999        vec!["completion_a", "completion_b"],
7000    );
7001    handle_copilot_completion_request(
7002        &copilot_lsp,
7003        vec![copilot::request::Completion {
7004            text: "one.copilot1".into(),
7005            range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7006            ..Default::default()
7007        }],
7008        vec![],
7009    );
7010    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7011    cx.update_editor(|editor, cx| {
7012        assert!(editor.context_menu_visible());
7013        assert!(!editor.has_active_copilot_suggestion(cx));
7014
7015        // When hiding the context menu, the Copilot suggestion becomes visible.
7016        editor.hide_context_menu(cx);
7017        assert!(!editor.context_menu_visible());
7018        assert!(editor.has_active_copilot_suggestion(cx));
7019        assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7020        assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
7021    });
7022
7023    // Ensure existing completion is interpolated when inserting again.
7024    cx.simulate_keystroke("c");
7025    deterministic.run_until_parked();
7026    cx.update_editor(|editor, cx| {
7027        assert!(!editor.context_menu_visible());
7028        assert!(editor.has_active_copilot_suggestion(cx));
7029        assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7030        assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7031    });
7032
7033    // After debouncing, new Copilot completions should be requested.
7034    handle_copilot_completion_request(
7035        &copilot_lsp,
7036        vec![copilot::request::Completion {
7037            text: "one.copilot2".into(),
7038            range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
7039            ..Default::default()
7040        }],
7041        vec![],
7042    );
7043    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7044    cx.update_editor(|editor, cx| {
7045        assert!(!editor.context_menu_visible());
7046        assert!(editor.has_active_copilot_suggestion(cx));
7047        assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7048        assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7049
7050        // Canceling should remove the active Copilot suggestion.
7051        editor.cancel(&Default::default(), cx);
7052        assert!(!editor.has_active_copilot_suggestion(cx));
7053        assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
7054        assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7055
7056        // After canceling, tabbing shouldn't insert the previously shown suggestion.
7057        editor.tab(&Default::default(), cx);
7058        assert!(!editor.has_active_copilot_suggestion(cx));
7059        assert_eq!(editor.display_text(cx), "one.c   \ntwo\nthree\n");
7060        assert_eq!(editor.text(cx), "one.c   \ntwo\nthree\n");
7061
7062        // When undoing the previously active suggestion is shown again.
7063        editor.undo(&Default::default(), cx);
7064        assert!(editor.has_active_copilot_suggestion(cx));
7065        assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7066        assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7067    });
7068
7069    // If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
7070    cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
7071    cx.update_editor(|editor, cx| {
7072        assert!(editor.has_active_copilot_suggestion(cx));
7073        assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7074        assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7075
7076        // Tabbing when there is an active suggestion inserts it.
7077        editor.tab(&Default::default(), cx);
7078        assert!(!editor.has_active_copilot_suggestion(cx));
7079        assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7080        assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
7081
7082        // When undoing the previously active suggestion is shown again.
7083        editor.undo(&Default::default(), cx);
7084        assert!(editor.has_active_copilot_suggestion(cx));
7085        assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7086        assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7087
7088        // Hide suggestion.
7089        editor.cancel(&Default::default(), cx);
7090        assert!(!editor.has_active_copilot_suggestion(cx));
7091        assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
7092        assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7093    });
7094
7095    // If an edit occurs outside of this editor but no suggestion is being shown,
7096    // we won't make it visible.
7097    cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
7098    cx.update_editor(|editor, cx| {
7099        assert!(!editor.has_active_copilot_suggestion(cx));
7100        assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
7101        assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
7102    });
7103
7104    // Reset the editor to verify how suggestions behave when tabbing on leading indentation.
7105    cx.update_editor(|editor, cx| {
7106        editor.set_text("fn foo() {\n  \n}", cx);
7107        editor.change_selections(None, cx, |s| {
7108            s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
7109        });
7110    });
7111    handle_copilot_completion_request(
7112        &copilot_lsp,
7113        vec![copilot::request::Completion {
7114            text: "    let x = 4;".into(),
7115            range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
7116            ..Default::default()
7117        }],
7118        vec![],
7119    );
7120
7121    cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
7122    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7123    cx.update_editor(|editor, cx| {
7124        assert!(editor.has_active_copilot_suggestion(cx));
7125        assert_eq!(editor.display_text(cx), "fn foo() {\n    let x = 4;\n}");
7126        assert_eq!(editor.text(cx), "fn foo() {\n  \n}");
7127
7128        // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
7129        editor.tab(&Default::default(), cx);
7130        assert!(editor.has_active_copilot_suggestion(cx));
7131        assert_eq!(editor.text(cx), "fn foo() {\n    \n}");
7132        assert_eq!(editor.display_text(cx), "fn foo() {\n    let x = 4;\n}");
7133
7134        // Tabbing again accepts the suggestion.
7135        editor.tab(&Default::default(), cx);
7136        assert!(!editor.has_active_copilot_suggestion(cx));
7137        assert_eq!(editor.text(cx), "fn foo() {\n    let x = 4;\n}");
7138        assert_eq!(editor.display_text(cx), "fn foo() {\n    let x = 4;\n}");
7139    });
7140}
7141
7142#[gpui::test]
7143async fn test_copilot_completion_invalidation(
7144    deterministic: Arc<Deterministic>,
7145    cx: &mut gpui::TestAppContext,
7146) {
7147    init_test(cx, |_| {});
7148
7149    let (copilot, copilot_lsp) = Copilot::fake(cx);
7150    cx.update(|cx| cx.set_global(copilot));
7151    let mut cx = EditorLspTestContext::new_rust(
7152        lsp::ServerCapabilities {
7153            completion_provider: Some(lsp::CompletionOptions {
7154                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7155                ..Default::default()
7156            }),
7157            ..Default::default()
7158        },
7159        cx,
7160    )
7161    .await;
7162
7163    cx.set_state(indoc! {"
7164        one
7165        twˇ
7166        three
7167    "});
7168
7169    handle_copilot_completion_request(
7170        &copilot_lsp,
7171        vec![copilot::request::Completion {
7172            text: "two.foo()".into(),
7173            range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
7174            ..Default::default()
7175        }],
7176        vec![],
7177    );
7178    cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
7179    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7180    cx.update_editor(|editor, cx| {
7181        assert!(editor.has_active_copilot_suggestion(cx));
7182        assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7183        assert_eq!(editor.text(cx), "one\ntw\nthree\n");
7184
7185        editor.backspace(&Default::default(), cx);
7186        assert!(editor.has_active_copilot_suggestion(cx));
7187        assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7188        assert_eq!(editor.text(cx), "one\nt\nthree\n");
7189
7190        editor.backspace(&Default::default(), cx);
7191        assert!(editor.has_active_copilot_suggestion(cx));
7192        assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7193        assert_eq!(editor.text(cx), "one\n\nthree\n");
7194
7195        // Deleting across the original suggestion range invalidates it.
7196        editor.backspace(&Default::default(), cx);
7197        assert!(!editor.has_active_copilot_suggestion(cx));
7198        assert_eq!(editor.display_text(cx), "one\nthree\n");
7199        assert_eq!(editor.text(cx), "one\nthree\n");
7200
7201        // Undoing the deletion restores the suggestion.
7202        editor.undo(&Default::default(), cx);
7203        assert!(editor.has_active_copilot_suggestion(cx));
7204        assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7205        assert_eq!(editor.text(cx), "one\n\nthree\n");
7206    });
7207}
7208
7209#[gpui::test]
7210async fn test_copilot_multibuffer(
7211    deterministic: Arc<Deterministic>,
7212    cx: &mut gpui::TestAppContext,
7213) {
7214    init_test(cx, |_| {});
7215
7216    let (copilot, copilot_lsp) = Copilot::fake(cx);
7217    cx.update(|cx| cx.set_global(copilot));
7218
7219    let buffer_1 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "a = 1\nb = 2\n"));
7220    let buffer_2 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "c = 3\nd = 4\n"));
7221    let multibuffer = cx.add_model(|cx| {
7222        let mut multibuffer = MultiBuffer::new(0);
7223        multibuffer.push_excerpts(
7224            buffer_1.clone(),
7225            [ExcerptRange {
7226                context: Point::new(0, 0)..Point::new(2, 0),
7227                primary: None,
7228            }],
7229            cx,
7230        );
7231        multibuffer.push_excerpts(
7232            buffer_2.clone(),
7233            [ExcerptRange {
7234                context: Point::new(0, 0)..Point::new(2, 0),
7235                primary: None,
7236            }],
7237            cx,
7238        );
7239        multibuffer
7240    });
7241    let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
7242
7243    handle_copilot_completion_request(
7244        &copilot_lsp,
7245        vec![copilot::request::Completion {
7246            text: "b = 2 + a".into(),
7247            range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
7248            ..Default::default()
7249        }],
7250        vec![],
7251    );
7252    editor.update(cx, |editor, cx| {
7253        // Ensure copilot suggestions are shown for the first excerpt.
7254        editor.change_selections(None, cx, |s| {
7255            s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
7256        });
7257        editor.next_copilot_suggestion(&Default::default(), cx);
7258    });
7259    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7260    editor.update(cx, |editor, cx| {
7261        assert!(editor.has_active_copilot_suggestion(cx));
7262        assert_eq!(
7263            editor.display_text(cx),
7264            "\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n"
7265        );
7266        assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
7267    });
7268
7269    handle_copilot_completion_request(
7270        &copilot_lsp,
7271        vec![copilot::request::Completion {
7272            text: "d = 4 + c".into(),
7273            range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
7274            ..Default::default()
7275        }],
7276        vec![],
7277    );
7278    editor.update(cx, |editor, cx| {
7279        // Move to another excerpt, ensuring the suggestion gets cleared.
7280        editor.change_selections(None, cx, |s| {
7281            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
7282        });
7283        assert!(!editor.has_active_copilot_suggestion(cx));
7284        assert_eq!(
7285            editor.display_text(cx),
7286            "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n"
7287        );
7288        assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
7289
7290        // Type a character, ensuring we don't even try to interpolate the previous suggestion.
7291        editor.handle_input(" ", cx);
7292        assert!(!editor.has_active_copilot_suggestion(cx));
7293        assert_eq!(
7294            editor.display_text(cx),
7295            "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n"
7296        );
7297        assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
7298    });
7299
7300    // Ensure the new suggestion is displayed when the debounce timeout expires.
7301    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7302    editor.update(cx, |editor, cx| {
7303        assert!(editor.has_active_copilot_suggestion(cx));
7304        assert_eq!(
7305            editor.display_text(cx),
7306            "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n"
7307        );
7308        assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
7309    });
7310}
7311
7312#[gpui::test]
7313async fn test_copilot_disabled_globs(
7314    deterministic: Arc<Deterministic>,
7315    cx: &mut gpui::TestAppContext,
7316) {
7317    init_test(cx, |settings| {
7318        settings
7319            .copilot
7320            .get_or_insert(Default::default())
7321            .disabled_globs = Some(vec![".env*".to_string()]);
7322    });
7323
7324    let (copilot, copilot_lsp) = Copilot::fake(cx);
7325    cx.update(|cx| cx.set_global(copilot));
7326
7327    let fs = FakeFs::new(cx.background());
7328    fs.insert_tree(
7329        "/test",
7330        json!({
7331            ".env": "SECRET=something\n",
7332            "README.md": "hello\n"
7333        }),
7334    )
7335    .await;
7336    let project = Project::test(fs, ["/test".as_ref()], cx).await;
7337
7338    let private_buffer = project
7339        .update(cx, |project, cx| {
7340            project.open_local_buffer("/test/.env", cx)
7341        })
7342        .await
7343        .unwrap();
7344    let public_buffer = project
7345        .update(cx, |project, cx| {
7346            project.open_local_buffer("/test/README.md", cx)
7347        })
7348        .await
7349        .unwrap();
7350
7351    let multibuffer = cx.add_model(|cx| {
7352        let mut multibuffer = MultiBuffer::new(0);
7353        multibuffer.push_excerpts(
7354            private_buffer.clone(),
7355            [ExcerptRange {
7356                context: Point::new(0, 0)..Point::new(1, 0),
7357                primary: None,
7358            }],
7359            cx,
7360        );
7361        multibuffer.push_excerpts(
7362            public_buffer.clone(),
7363            [ExcerptRange {
7364                context: Point::new(0, 0)..Point::new(1, 0),
7365                primary: None,
7366            }],
7367            cx,
7368        );
7369        multibuffer
7370    });
7371    let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
7372
7373    let mut copilot_requests = copilot_lsp
7374        .handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
7375            Ok(copilot::request::GetCompletionsResult {
7376                completions: vec![copilot::request::Completion {
7377                    text: "next line".into(),
7378                    range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7379                    ..Default::default()
7380                }],
7381            })
7382        });
7383
7384    editor.update(cx, |editor, cx| {
7385        editor.change_selections(None, cx, |selections| {
7386            selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
7387        });
7388        editor.next_copilot_suggestion(&Default::default(), cx);
7389    });
7390
7391    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7392    assert!(copilot_requests.try_next().is_err());
7393
7394    editor.update(cx, |editor, cx| {
7395        editor.change_selections(None, cx, |s| {
7396            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
7397        });
7398        editor.next_copilot_suggestion(&Default::default(), cx);
7399    });
7400
7401    deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7402    assert!(copilot_requests.try_next().is_ok());
7403}
7404
7405#[gpui::test]
7406async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
7407    init_test(cx, |_| {});
7408
7409    let mut language = Language::new(
7410        LanguageConfig {
7411            name: "Rust".into(),
7412            path_suffixes: vec!["rs".to_string()],
7413            brackets: BracketPairConfig {
7414                pairs: vec![BracketPair {
7415                    start: "{".to_string(),
7416                    end: "}".to_string(),
7417                    close: true,
7418                    newline: true,
7419                }],
7420                disabled_scopes_by_bracket_ix: Vec::new(),
7421            },
7422            ..Default::default()
7423        },
7424        Some(tree_sitter_rust::language()),
7425    );
7426    let mut fake_servers = language
7427        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7428            capabilities: lsp::ServerCapabilities {
7429                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
7430                    first_trigger_character: "{".to_string(),
7431                    more_trigger_character: None,
7432                }),
7433                ..Default::default()
7434            },
7435            ..Default::default()
7436        }))
7437        .await;
7438
7439    let fs = FakeFs::new(cx.background());
7440    fs.insert_tree(
7441        "/a",
7442        json!({
7443            "main.rs": "fn main() { let a = 5; }",
7444            "other.rs": "// Test file",
7445        }),
7446    )
7447    .await;
7448    let project = Project::test(fs, ["/a".as_ref()], cx).await;
7449    project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7450    let workspace = cx
7451        .add_window(|cx| Workspace::test_new(project.clone(), cx))
7452        .root(cx);
7453    let worktree_id = workspace.update(cx, |workspace, cx| {
7454        workspace.project().read_with(cx, |project, cx| {
7455            project.worktrees(cx).next().unwrap().read(cx).id()
7456        })
7457    });
7458
7459    let buffer = project
7460        .update(cx, |project, cx| {
7461            project.open_local_buffer("/a/main.rs", cx)
7462        })
7463        .await
7464        .unwrap();
7465    cx.foreground().run_until_parked();
7466    cx.foreground().start_waiting();
7467    let fake_server = fake_servers.next().await.unwrap();
7468    let editor_handle = workspace
7469        .update(cx, |workspace, cx| {
7470            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
7471        })
7472        .await
7473        .unwrap()
7474        .downcast::<Editor>()
7475        .unwrap();
7476
7477    fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
7478        assert_eq!(
7479            params.text_document_position.text_document.uri,
7480            lsp::Url::from_file_path("/a/main.rs").unwrap(),
7481        );
7482        assert_eq!(
7483            params.text_document_position.position,
7484            lsp::Position::new(0, 21),
7485        );
7486
7487        Ok(Some(vec![lsp::TextEdit {
7488            new_text: "]".to_string(),
7489            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
7490        }]))
7491    });
7492
7493    editor_handle.update(cx, |editor, cx| {
7494        cx.focus(&editor_handle);
7495        editor.change_selections(None, cx, |s| {
7496            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
7497        });
7498        editor.handle_input("{", cx);
7499    });
7500
7501    cx.foreground().run_until_parked();
7502
7503    buffer.read_with(cx, |buffer, _| {
7504        assert_eq!(
7505            buffer.text(),
7506            "fn main() { let a = {5}; }",
7507            "No extra braces from on type formatting should appear in the buffer"
7508        )
7509    });
7510}
7511
7512#[gpui::test]
7513async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
7514    init_test(cx, |_| {});
7515
7516    let language_name: Arc<str> = "Rust".into();
7517    let mut language = Language::new(
7518        LanguageConfig {
7519            name: Arc::clone(&language_name),
7520            path_suffixes: vec!["rs".to_string()],
7521            ..Default::default()
7522        },
7523        Some(tree_sitter_rust::language()),
7524    );
7525
7526    let server_restarts = Arc::new(AtomicUsize::new(0));
7527    let closure_restarts = Arc::clone(&server_restarts);
7528    let language_server_name = "test language server";
7529    let mut fake_servers = language
7530        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7531            name: language_server_name,
7532            initialization_options: Some(json!({
7533                "testOptionValue": true
7534            })),
7535            initializer: Some(Box::new(move |fake_server| {
7536                let task_restarts = Arc::clone(&closure_restarts);
7537                fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
7538                    task_restarts.fetch_add(1, atomic::Ordering::Release);
7539                    futures::future::ready(Ok(()))
7540                });
7541            })),
7542            ..Default::default()
7543        }))
7544        .await;
7545
7546    let fs = FakeFs::new(cx.background());
7547    fs.insert_tree(
7548        "/a",
7549        json!({
7550            "main.rs": "fn main() { let a = 5; }",
7551            "other.rs": "// Test file",
7552        }),
7553    )
7554    .await;
7555    let project = Project::test(fs, ["/a".as_ref()], cx).await;
7556    project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7557    let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7558    let _buffer = project
7559        .update(cx, |project, cx| {
7560            project.open_local_buffer("/a/main.rs", cx)
7561        })
7562        .await
7563        .unwrap();
7564    let _fake_server = fake_servers.next().await.unwrap();
7565    update_test_language_settings(cx, |language_settings| {
7566        language_settings.languages.insert(
7567            Arc::clone(&language_name),
7568            LanguageSettingsContent {
7569                tab_size: NonZeroU32::new(8),
7570                ..Default::default()
7571            },
7572        );
7573    });
7574    cx.foreground().run_until_parked();
7575    assert_eq!(
7576        server_restarts.load(atomic::Ordering::Acquire),
7577        0,
7578        "Should not restart LSP server on an unrelated change"
7579    );
7580
7581    update_test_project_settings(cx, |project_settings| {
7582        project_settings.lsp.insert(
7583            "Some other server name".into(),
7584            LspSettings {
7585                initialization_options: Some(json!({
7586                    "some other init value": false
7587                })),
7588            },
7589        );
7590    });
7591    cx.foreground().run_until_parked();
7592    assert_eq!(
7593        server_restarts.load(atomic::Ordering::Acquire),
7594        0,
7595        "Should not restart LSP server on an unrelated LSP settings change"
7596    );
7597
7598    update_test_project_settings(cx, |project_settings| {
7599        project_settings.lsp.insert(
7600            language_server_name.into(),
7601            LspSettings {
7602                initialization_options: Some(json!({
7603                    "anotherInitValue": false
7604                })),
7605            },
7606        );
7607    });
7608    cx.foreground().run_until_parked();
7609    assert_eq!(
7610        server_restarts.load(atomic::Ordering::Acquire),
7611        1,
7612        "Should restart LSP server on a related LSP settings change"
7613    );
7614
7615    update_test_project_settings(cx, |project_settings| {
7616        project_settings.lsp.insert(
7617            language_server_name.into(),
7618            LspSettings {
7619                initialization_options: Some(json!({
7620                    "anotherInitValue": false
7621                })),
7622            },
7623        );
7624    });
7625    cx.foreground().run_until_parked();
7626    assert_eq!(
7627        server_restarts.load(atomic::Ordering::Acquire),
7628        1,
7629        "Should not restart LSP server on a related LSP settings change that is the same"
7630    );
7631
7632    update_test_project_settings(cx, |project_settings| {
7633        project_settings.lsp.insert(
7634            language_server_name.into(),
7635            LspSettings {
7636                initialization_options: None,
7637            },
7638        );
7639    });
7640    cx.foreground().run_until_parked();
7641    assert_eq!(
7642        server_restarts.load(atomic::Ordering::Acquire),
7643        2,
7644        "Should restart LSP server on another related LSP settings change"
7645    );
7646}
7647
7648#[gpui::test]
7649async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
7650    init_test(cx, |_| {});
7651
7652    let mut cx = EditorLspTestContext::new_rust(
7653        lsp::ServerCapabilities {
7654            completion_provider: Some(lsp::CompletionOptions {
7655                trigger_characters: Some(vec![".".to_string()]),
7656                resolve_provider: Some(true),
7657                ..Default::default()
7658            }),
7659            ..Default::default()
7660        },
7661        cx,
7662    )
7663    .await;
7664
7665    cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7666    cx.simulate_keystroke(".");
7667    let completion_item = lsp::CompletionItem {
7668        label: "some".into(),
7669        kind: Some(lsp::CompletionItemKind::SNIPPET),
7670        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7671        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7672            kind: lsp::MarkupKind::Markdown,
7673            value: "```rust\nSome(2)\n```".to_string(),
7674        })),
7675        deprecated: Some(false),
7676        sort_text: Some("fffffff2".to_string()),
7677        filter_text: Some("some".to_string()),
7678        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7679        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7680            range: lsp::Range {
7681                start: lsp::Position {
7682                    line: 0,
7683                    character: 22,
7684                },
7685                end: lsp::Position {
7686                    line: 0,
7687                    character: 22,
7688                },
7689            },
7690            new_text: "Some(2)".to_string(),
7691        })),
7692        additional_text_edits: Some(vec![lsp::TextEdit {
7693            range: lsp::Range {
7694                start: lsp::Position {
7695                    line: 0,
7696                    character: 20,
7697                },
7698                end: lsp::Position {
7699                    line: 0,
7700                    character: 22,
7701                },
7702            },
7703            new_text: "".to_string(),
7704        }]),
7705        ..Default::default()
7706    };
7707
7708    let closure_completion_item = completion_item.clone();
7709    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7710        let task_completion_item = closure_completion_item.clone();
7711        async move {
7712            Ok(Some(lsp::CompletionResponse::Array(vec![
7713                task_completion_item,
7714            ])))
7715        }
7716    });
7717
7718    request.next().await;
7719
7720    cx.condition(|editor, _| editor.context_menu_visible())
7721        .await;
7722    let apply_additional_edits = cx.update_editor(|editor, cx| {
7723        editor
7724            .confirm_completion(&ConfirmCompletion::default(), cx)
7725            .unwrap()
7726    });
7727    cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
7728
7729    cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
7730        let task_completion_item = completion_item.clone();
7731        async move { Ok(task_completion_item) }
7732    })
7733    .next()
7734    .await
7735    .unwrap();
7736    apply_additional_edits.await.unwrap();
7737    cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
7738}
7739
7740#[gpui::test]
7741async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
7742    init_test(cx, |_| {});
7743
7744    let mut cx = EditorLspTestContext::new(
7745        Language::new(
7746            LanguageConfig {
7747                path_suffixes: vec!["jsx".into()],
7748                overrides: [(
7749                    "element".into(),
7750                    LanguageConfigOverride {
7751                        word_characters: Override::Set(['-'].into_iter().collect()),
7752                        ..Default::default()
7753                    },
7754                )]
7755                .into_iter()
7756                .collect(),
7757                ..Default::default()
7758            },
7759            Some(tree_sitter_typescript::language_tsx()),
7760        )
7761        .with_override_query("(jsx_self_closing_element) @element")
7762        .unwrap(),
7763        lsp::ServerCapabilities {
7764            completion_provider: Some(lsp::CompletionOptions {
7765                trigger_characters: Some(vec![":".to_string()]),
7766                ..Default::default()
7767            }),
7768            ..Default::default()
7769        },
7770        cx,
7771    )
7772    .await;
7773
7774    cx.lsp
7775        .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
7776            Ok(Some(lsp::CompletionResponse::Array(vec![
7777                lsp::CompletionItem {
7778                    label: "bg-blue".into(),
7779                    ..Default::default()
7780                },
7781                lsp::CompletionItem {
7782                    label: "bg-red".into(),
7783                    ..Default::default()
7784                },
7785                lsp::CompletionItem {
7786                    label: "bg-yellow".into(),
7787                    ..Default::default()
7788                },
7789            ])))
7790        });
7791
7792    cx.set_state(r#"<p class="bgˇ" />"#);
7793
7794    // Trigger completion when typing a dash, because the dash is an extra
7795    // word character in the 'element' scope, which contains the cursor.
7796    cx.simulate_keystroke("-");
7797    cx.foreground().run_until_parked();
7798    cx.update_editor(|editor, _| {
7799        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7800            assert_eq!(
7801                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7802                &["bg-red", "bg-blue", "bg-yellow"]
7803            );
7804        } else {
7805            panic!("expected completion menu to be open");
7806        }
7807    });
7808
7809    cx.simulate_keystroke("l");
7810    cx.foreground().run_until_parked();
7811    cx.update_editor(|editor, _| {
7812        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7813            assert_eq!(
7814                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7815                &["bg-blue", "bg-yellow"]
7816            );
7817        } else {
7818            panic!("expected completion menu to be open");
7819        }
7820    });
7821
7822    // When filtering completions, consider the character after the '-' to
7823    // be the start of a subword.
7824    cx.set_state(r#"<p class="yelˇ" />"#);
7825    cx.simulate_keystroke("l");
7826    cx.foreground().run_until_parked();
7827    cx.update_editor(|editor, _| {
7828        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7829            assert_eq!(
7830                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7831                &["bg-yellow"]
7832            );
7833        } else {
7834            panic!("expected completion menu to be open");
7835        }
7836    });
7837}
7838
7839#[gpui::test]
7840async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
7841    init_test(cx, |settings| {
7842        settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
7843    });
7844
7845    let mut language = Language::new(
7846        LanguageConfig {
7847            name: "Rust".into(),
7848            path_suffixes: vec!["rs".to_string()],
7849            prettier_parser_name: Some("test_parser".to_string()),
7850            ..Default::default()
7851        },
7852        Some(tree_sitter_rust::language()),
7853    );
7854
7855    let test_plugin = "test_plugin";
7856    let _ = language
7857        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7858            prettier_plugins: vec![test_plugin],
7859            ..Default::default()
7860        }))
7861        .await;
7862
7863    let fs = FakeFs::new(cx.background());
7864    fs.insert_file("/file.rs", Default::default()).await;
7865
7866    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7867    let prettier_format_suffix = project.update(cx, |project, _| {
7868        let suffix = project.enable_test_prettier(&[test_plugin]);
7869        project.languages().add(Arc::new(language));
7870        suffix
7871    });
7872    let buffer = project
7873        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7874        .await
7875        .unwrap();
7876
7877    let buffer_text = "one\ntwo\nthree\n";
7878    let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
7879    let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
7880    editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
7881
7882    let format = editor.update(cx, |editor, cx| {
7883        editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
7884    });
7885    format.await.unwrap();
7886    assert_eq!(
7887        editor.read_with(cx, |editor, cx| editor.text(cx)),
7888        buffer_text.to_string() + prettier_format_suffix,
7889        "Test prettier formatting was not applied to the original buffer text",
7890    );
7891
7892    update_test_language_settings(cx, |settings| {
7893        settings.defaults.formatter = Some(language_settings::Formatter::Auto)
7894    });
7895    let format = editor.update(cx, |editor, cx| {
7896        editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
7897    });
7898    format.await.unwrap();
7899    assert_eq!(
7900        editor.read_with(cx, |editor, cx| editor.text(cx)),
7901        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
7902        "Autoformatting (via test prettier) was not applied to the original buffer text",
7903    );
7904}
7905
7906fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
7907    let point = DisplayPoint::new(row as u32, column as u32);
7908    point..point
7909}
7910
7911fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
7912    let (text, ranges) = marked_text_ranges(marked_text, true);
7913    assert_eq!(view.text(cx), text);
7914    assert_eq!(
7915        view.selections.ranges(cx),
7916        ranges,
7917        "Assert selections are {}",
7918        marked_text
7919    );
7920}
7921
7922/// Handle completion request passing a marked string specifying where the completion
7923/// should be triggered from using '|' character, what range should be replaced, and what completions
7924/// should be returned using '<' and '>' to delimit the range
7925pub fn handle_completion_request<'a>(
7926    cx: &mut EditorLspTestContext<'a>,
7927    marked_string: &str,
7928    completions: Vec<&'static str>,
7929) -> impl Future<Output = ()> {
7930    let complete_from_marker: TextRangeMarker = '|'.into();
7931    let replace_range_marker: TextRangeMarker = ('<', '>').into();
7932    let (_, mut marked_ranges) = marked_text_ranges_by(
7933        marked_string,
7934        vec![complete_from_marker.clone(), replace_range_marker.clone()],
7935    );
7936
7937    let complete_from_position =
7938        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
7939    let replace_range =
7940        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
7941
7942    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
7943        let completions = completions.clone();
7944        async move {
7945            assert_eq!(params.text_document_position.text_document.uri, url.clone());
7946            assert_eq!(
7947                params.text_document_position.position,
7948                complete_from_position
7949            );
7950            Ok(Some(lsp::CompletionResponse::Array(
7951                completions
7952                    .iter()
7953                    .map(|completion_text| lsp::CompletionItem {
7954                        label: completion_text.to_string(),
7955                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7956                            range: replace_range,
7957                            new_text: completion_text.to_string(),
7958                        })),
7959                        ..Default::default()
7960                    })
7961                    .collect(),
7962            )))
7963        }
7964    });
7965
7966    async move {
7967        request.next().await;
7968    }
7969}
7970
7971fn handle_resolve_completion_request<'a>(
7972    cx: &mut EditorLspTestContext<'a>,
7973    edits: Option<Vec<(&'static str, &'static str)>>,
7974) -> impl Future<Output = ()> {
7975    let edits = edits.map(|edits| {
7976        edits
7977            .iter()
7978            .map(|(marked_string, new_text)| {
7979                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
7980                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
7981                lsp::TextEdit::new(replace_range, new_text.to_string())
7982            })
7983            .collect::<Vec<_>>()
7984    });
7985
7986    let mut request =
7987        cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
7988            let edits = edits.clone();
7989            async move {
7990                Ok(lsp::CompletionItem {
7991                    additional_text_edits: edits,
7992                    ..Default::default()
7993                })
7994            }
7995        });
7996
7997    async move {
7998        request.next().await;
7999    }
8000}
8001
8002fn handle_copilot_completion_request(
8003    lsp: &lsp::FakeLanguageServer,
8004    completions: Vec<copilot::request::Completion>,
8005    completions_cycling: Vec<copilot::request::Completion>,
8006) {
8007    lsp.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| {
8008        let completions = completions.clone();
8009        async move {
8010            Ok(copilot::request::GetCompletionsResult {
8011                completions: completions.clone(),
8012            })
8013        }
8014    });
8015    lsp.handle_request::<copilot::request::GetCompletionsCycling, _, _>(move |_params, _cx| {
8016        let completions_cycling = completions_cycling.clone();
8017        async move {
8018            Ok(copilot::request::GetCompletionsResult {
8019                completions: completions_cycling.clone(),
8020            })
8021        }
8022    });
8023}
8024
8025pub(crate) fn update_test_language_settings(
8026    cx: &mut TestAppContext,
8027    f: impl Fn(&mut AllLanguageSettingsContent),
8028) {
8029    cx.update(|cx| {
8030        cx.update_global::<SettingsStore, _, _>(|store, cx| {
8031            store.update_user_settings::<AllLanguageSettings>(cx, f);
8032        });
8033    });
8034}
8035
8036pub(crate) fn update_test_project_settings(
8037    cx: &mut TestAppContext,
8038    f: impl Fn(&mut ProjectSettings),
8039) {
8040    cx.update(|cx| {
8041        cx.update_global::<SettingsStore, _, _>(|store, cx| {
8042            store.update_user_settings::<ProjectSettings>(cx, f);
8043        });
8044    });
8045}
8046
8047pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
8048    cx.foreground().forbid_parking();
8049
8050    cx.update(|cx| {
8051        cx.set_global(SettingsStore::test(cx));
8052        theme::init((), cx);
8053        client::init_settings(cx);
8054        language::init(cx);
8055        Project::init_settings(cx);
8056        workspace::init_settings(cx);
8057        crate::init(cx);
8058    });
8059
8060    update_test_language_settings(cx, f);
8061}