editor_tests.rs

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