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