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