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