editor_tests.rs

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