editor_tests.rs

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