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
7360            .create_buffer(&sample_text(16, 8, 'a'), None, cx)
7361            .unwrap();
7362        cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
7363    });
7364    let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
7365    let follower = cx.update(|cx| {
7366        cx.open_window(
7367            WindowOptions {
7368                bounds: Some(Bounds::from_corners(
7369                    gpui::Point::new(0.into(), 0.into()),
7370                    gpui::Point::new(10.into(), 80.into()),
7371                )),
7372                ..Default::default()
7373            },
7374            |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
7375        )
7376    });
7377
7378    let is_still_following = Rc::new(RefCell::new(true));
7379    let follower_edit_event_count = Rc::new(RefCell::new(0));
7380    let pending_update = Rc::new(RefCell::new(None));
7381    _ = follower.update(cx, {
7382        let update = pending_update.clone();
7383        let is_still_following = is_still_following.clone();
7384        let follower_edit_event_count = follower_edit_event_count.clone();
7385        |_, cx| {
7386            cx.subscribe(
7387                &leader.root_view(cx).unwrap(),
7388                move |_, leader, event, cx| {
7389                    leader
7390                        .read(cx)
7391                        .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
7392                },
7393            )
7394            .detach();
7395
7396            cx.subscribe(
7397                &follower.root_view(cx).unwrap(),
7398                move |_, _, event: &EditorEvent, _cx| {
7399                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
7400                        *is_still_following.borrow_mut() = false;
7401                    }
7402
7403                    if let EditorEvent::BufferEdited = event {
7404                        *follower_edit_event_count.borrow_mut() += 1;
7405                    }
7406                },
7407            )
7408            .detach();
7409        }
7410    });
7411
7412    // Update the selections only
7413    _ = leader.update(cx, |leader, cx| {
7414        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
7415    });
7416    follower
7417        .update(cx, |follower, cx| {
7418            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7419        })
7420        .unwrap()
7421        .await
7422        .unwrap();
7423    _ = follower.update(cx, |follower, cx| {
7424        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
7425    });
7426    assert_eq!(*is_still_following.borrow(), true);
7427    assert_eq!(*follower_edit_event_count.borrow(), 0);
7428
7429    // Update the scroll position only
7430    _ = leader.update(cx, |leader, cx| {
7431        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
7432    });
7433    follower
7434        .update(cx, |follower, cx| {
7435            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7436        })
7437        .unwrap()
7438        .await
7439        .unwrap();
7440    assert_eq!(
7441        follower
7442            .update(cx, |follower, cx| follower.scroll_position(cx))
7443            .unwrap(),
7444        gpui::Point::new(1.5, 3.5)
7445    );
7446    assert_eq!(*is_still_following.borrow(), true);
7447    assert_eq!(*follower_edit_event_count.borrow(), 0);
7448
7449    // Update the selections and scroll position. The follower's scroll position is updated
7450    // via autoscroll, not via the leader's exact scroll position.
7451    _ = leader.update(cx, |leader, cx| {
7452        leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
7453        leader.request_autoscroll(Autoscroll::newest(), cx);
7454        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
7455    });
7456    follower
7457        .update(cx, |follower, cx| {
7458            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7459        })
7460        .unwrap()
7461        .await
7462        .unwrap();
7463    _ = follower.update(cx, |follower, cx| {
7464        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
7465        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
7466    });
7467    assert_eq!(*is_still_following.borrow(), true);
7468
7469    // Creating a pending selection that precedes another selection
7470    _ = leader.update(cx, |leader, cx| {
7471        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
7472        leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
7473    });
7474    follower
7475        .update(cx, |follower, cx| {
7476            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7477        })
7478        .unwrap()
7479        .await
7480        .unwrap();
7481    _ = follower.update(cx, |follower, cx| {
7482        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
7483    });
7484    assert_eq!(*is_still_following.borrow(), true);
7485
7486    // Extend the pending selection so that it surrounds another selection
7487    _ = leader.update(cx, |leader, cx| {
7488        leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
7489    });
7490    follower
7491        .update(cx, |follower, cx| {
7492            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7493        })
7494        .unwrap()
7495        .await
7496        .unwrap();
7497    _ = follower.update(cx, |follower, cx| {
7498        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
7499    });
7500
7501    // Scrolling locally breaks the follow
7502    _ = follower.update(cx, |follower, cx| {
7503        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
7504        follower.set_scroll_anchor(
7505            ScrollAnchor {
7506                anchor: top_anchor,
7507                offset: gpui::Point::new(0.0, 0.5),
7508            },
7509            cx,
7510        );
7511    });
7512    assert_eq!(*is_still_following.borrow(), false);
7513}
7514
7515#[gpui::test]
7516async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
7517    init_test(cx, |_| {});
7518
7519    let fs = FakeFs::new(cx.executor());
7520    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7521    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7522    let pane = workspace
7523        .update(cx, |workspace, _| workspace.active_pane().clone())
7524        .unwrap();
7525
7526    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7527
7528    let leader = pane.update(cx, |_, cx| {
7529        let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
7530        cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
7531    });
7532
7533    // Start following the editor when it has no excerpts.
7534    let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
7535    let follower_1 = cx
7536        .update_window(*workspace.deref(), |_, cx| {
7537            Editor::from_state_proto(
7538                pane.clone(),
7539                workspace.root_view(cx).unwrap(),
7540                ViewId {
7541                    creator: Default::default(),
7542                    id: 0,
7543                },
7544                &mut state_message,
7545                cx,
7546            )
7547        })
7548        .unwrap()
7549        .unwrap()
7550        .await
7551        .unwrap();
7552
7553    let update_message = Rc::new(RefCell::new(None));
7554    follower_1.update(cx, {
7555        let update = update_message.clone();
7556        |_, cx| {
7557            cx.subscribe(&leader, move |_, leader, event, cx| {
7558                leader
7559                    .read(cx)
7560                    .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
7561            })
7562            .detach();
7563        }
7564    });
7565
7566    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
7567        (
7568            project
7569                .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
7570                .unwrap(),
7571            project
7572                .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
7573                .unwrap(),
7574        )
7575    });
7576
7577    // Insert some excerpts.
7578    _ = leader.update(cx, |leader, cx| {
7579        leader.buffer.update(cx, |multibuffer, cx| {
7580            let excerpt_ids = multibuffer.push_excerpts(
7581                buffer_1.clone(),
7582                [
7583                    ExcerptRange {
7584                        context: 1..6,
7585                        primary: None,
7586                    },
7587                    ExcerptRange {
7588                        context: 12..15,
7589                        primary: None,
7590                    },
7591                    ExcerptRange {
7592                        context: 0..3,
7593                        primary: None,
7594                    },
7595                ],
7596                cx,
7597            );
7598            multibuffer.insert_excerpts_after(
7599                excerpt_ids[0],
7600                buffer_2.clone(),
7601                [
7602                    ExcerptRange {
7603                        context: 8..12,
7604                        primary: None,
7605                    },
7606                    ExcerptRange {
7607                        context: 0..6,
7608                        primary: None,
7609                    },
7610                ],
7611                cx,
7612            );
7613        });
7614    });
7615
7616    // Apply the update of adding the excerpts.
7617    follower_1
7618        .update(cx, |follower, cx| {
7619            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
7620        })
7621        .await
7622        .unwrap();
7623    assert_eq!(
7624        follower_1.update(cx, |editor, cx| editor.text(cx)),
7625        leader.update(cx, |editor, cx| editor.text(cx))
7626    );
7627    update_message.borrow_mut().take();
7628
7629    // Start following separately after it already has excerpts.
7630    let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
7631    let follower_2 = cx
7632        .update_window(*workspace.deref(), |_, cx| {
7633            Editor::from_state_proto(
7634                pane.clone(),
7635                workspace.root_view(cx).unwrap().clone(),
7636                ViewId {
7637                    creator: Default::default(),
7638                    id: 0,
7639                },
7640                &mut state_message,
7641                cx,
7642            )
7643        })
7644        .unwrap()
7645        .unwrap()
7646        .await
7647        .unwrap();
7648    assert_eq!(
7649        follower_2.update(cx, |editor, cx| editor.text(cx)),
7650        leader.update(cx, |editor, cx| editor.text(cx))
7651    );
7652
7653    // Remove some excerpts.
7654    _ = leader.update(cx, |leader, cx| {
7655        leader.buffer.update(cx, |multibuffer, cx| {
7656            let excerpt_ids = multibuffer.excerpt_ids();
7657            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
7658            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
7659        });
7660    });
7661
7662    // Apply the update of removing the excerpts.
7663    follower_1
7664        .update(cx, |follower, cx| {
7665            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
7666        })
7667        .await
7668        .unwrap();
7669    follower_2
7670        .update(cx, |follower, cx| {
7671            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
7672        })
7673        .await
7674        .unwrap();
7675    update_message.borrow_mut().take();
7676    assert_eq!(
7677        follower_1.update(cx, |editor, cx| editor.text(cx)),
7678        leader.update(cx, |editor, cx| editor.text(cx))
7679    );
7680}
7681
7682#[gpui::test]
7683async fn go_to_prev_overlapping_diagnostic(
7684    executor: BackgroundExecutor,
7685    cx: &mut gpui::TestAppContext,
7686) {
7687    init_test(cx, |_| {});
7688
7689    let mut cx = EditorTestContext::new(cx).await;
7690    let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
7691
7692    cx.set_state(indoc! {"
7693        ˇfn func(abc def: i32) -> u32 {
7694        }
7695    "});
7696
7697    _ = cx.update(|cx| {
7698        _ = project.update(cx, |project, cx| {
7699            project
7700                .update_diagnostics(
7701                    LanguageServerId(0),
7702                    lsp::PublishDiagnosticsParams {
7703                        uri: lsp::Url::from_file_path("/root/file").unwrap(),
7704                        version: None,
7705                        diagnostics: vec![
7706                            lsp::Diagnostic {
7707                                range: lsp::Range::new(
7708                                    lsp::Position::new(0, 11),
7709                                    lsp::Position::new(0, 12),
7710                                ),
7711                                severity: Some(lsp::DiagnosticSeverity::ERROR),
7712                                ..Default::default()
7713                            },
7714                            lsp::Diagnostic {
7715                                range: lsp::Range::new(
7716                                    lsp::Position::new(0, 12),
7717                                    lsp::Position::new(0, 15),
7718                                ),
7719                                severity: Some(lsp::DiagnosticSeverity::ERROR),
7720                                ..Default::default()
7721                            },
7722                            lsp::Diagnostic {
7723                                range: lsp::Range::new(
7724                                    lsp::Position::new(0, 25),
7725                                    lsp::Position::new(0, 28),
7726                                ),
7727                                severity: Some(lsp::DiagnosticSeverity::ERROR),
7728                                ..Default::default()
7729                            },
7730                        ],
7731                    },
7732                    &[],
7733                    cx,
7734                )
7735                .unwrap()
7736        });
7737    });
7738
7739    executor.run_until_parked();
7740
7741    cx.update_editor(|editor, cx| {
7742        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
7743    });
7744
7745    cx.assert_editor_state(indoc! {"
7746        fn func(abc def: i32) -> ˇu32 {
7747        }
7748    "});
7749
7750    cx.update_editor(|editor, cx| {
7751        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
7752    });
7753
7754    cx.assert_editor_state(indoc! {"
7755        fn func(abc ˇdef: i32) -> u32 {
7756        }
7757    "});
7758
7759    cx.update_editor(|editor, cx| {
7760        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
7761    });
7762
7763    cx.assert_editor_state(indoc! {"
7764        fn func(abcˇ def: i32) -> u32 {
7765        }
7766    "});
7767
7768    cx.update_editor(|editor, cx| {
7769        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
7770    });
7771
7772    cx.assert_editor_state(indoc! {"
7773        fn func(abc def: i32) -> ˇu32 {
7774        }
7775    "});
7776}
7777
7778#[gpui::test]
7779async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7780    init_test(cx, |_| {});
7781
7782    let mut cx = EditorTestContext::new(cx).await;
7783
7784    let diff_base = r#"
7785        use some::mod;
7786
7787        const A: u32 = 42;
7788
7789        fn main() {
7790            println!("hello");
7791
7792            println!("world");
7793        }
7794        "#
7795    .unindent();
7796
7797    // Edits are modified, removed, modified, added
7798    cx.set_state(
7799        &r#"
7800        use some::modified;
7801
7802        ˇ
7803        fn main() {
7804            println!("hello there");
7805
7806            println!("around the");
7807            println!("world");
7808        }
7809        "#
7810        .unindent(),
7811    );
7812
7813    cx.set_diff_base(Some(&diff_base));
7814    executor.run_until_parked();
7815
7816    cx.update_editor(|editor, cx| {
7817        //Wrap around the bottom of the buffer
7818        for _ in 0..3 {
7819            editor.go_to_hunk(&GoToHunk, cx);
7820        }
7821    });
7822
7823    cx.assert_editor_state(
7824        &r#"
7825        ˇuse some::modified;
7826
7827
7828        fn main() {
7829            println!("hello there");
7830
7831            println!("around the");
7832            println!("world");
7833        }
7834        "#
7835        .unindent(),
7836    );
7837
7838    cx.update_editor(|editor, cx| {
7839        //Wrap around the top of the buffer
7840        for _ in 0..2 {
7841            editor.go_to_prev_hunk(&GoToPrevHunk, cx);
7842        }
7843    });
7844
7845    cx.assert_editor_state(
7846        &r#"
7847        use some::modified;
7848
7849
7850        fn main() {
7851        ˇ    println!("hello there");
7852
7853            println!("around the");
7854            println!("world");
7855        }
7856        "#
7857        .unindent(),
7858    );
7859
7860    cx.update_editor(|editor, cx| {
7861        editor.go_to_prev_hunk(&GoToPrevHunk, cx);
7862    });
7863
7864    cx.assert_editor_state(
7865        &r#"
7866        use some::modified;
7867
7868        ˇ
7869        fn main() {
7870            println!("hello there");
7871
7872            println!("around the");
7873            println!("world");
7874        }
7875        "#
7876        .unindent(),
7877    );
7878
7879    cx.update_editor(|editor, cx| {
7880        for _ in 0..3 {
7881            editor.go_to_prev_hunk(&GoToPrevHunk, cx);
7882        }
7883    });
7884
7885    cx.assert_editor_state(
7886        &r#"
7887        use some::modified;
7888
7889
7890        fn main() {
7891        ˇ    println!("hello there");
7892
7893            println!("around the");
7894            println!("world");
7895        }
7896        "#
7897        .unindent(),
7898    );
7899
7900    cx.update_editor(|editor, cx| {
7901        editor.fold(&Fold, cx);
7902
7903        //Make sure that the fold only gets one hunk
7904        for _ in 0..4 {
7905            editor.go_to_hunk(&GoToHunk, cx);
7906        }
7907    });
7908
7909    cx.assert_editor_state(
7910        &r#"
7911        ˇuse some::modified;
7912
7913
7914        fn main() {
7915            println!("hello there");
7916
7917            println!("around the");
7918            println!("world");
7919        }
7920        "#
7921        .unindent(),
7922    );
7923}
7924
7925#[test]
7926fn test_split_words() {
7927    fn split(text: &str) -> Vec<&str> {
7928        split_words(text).collect()
7929    }
7930
7931    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
7932    assert_eq!(split("hello_world"), &["hello_", "world"]);
7933    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
7934    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
7935    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
7936    assert_eq!(split("helloworld"), &["helloworld"]);
7937
7938    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
7939}
7940
7941#[gpui::test]
7942async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
7943    init_test(cx, |_| {});
7944
7945    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
7946    let mut assert = |before, after| {
7947        let _state_context = cx.set_state(before);
7948        cx.update_editor(|editor, cx| {
7949            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
7950        });
7951        cx.assert_editor_state(after);
7952    };
7953
7954    // Outside bracket jumps to outside of matching bracket
7955    assert("console.logˇ(var);", "console.log(var)ˇ;");
7956    assert("console.log(var)ˇ;", "console.logˇ(var);");
7957
7958    // Inside bracket jumps to inside of matching bracket
7959    assert("console.log(ˇvar);", "console.log(varˇ);");
7960    assert("console.log(varˇ);", "console.log(ˇvar);");
7961
7962    // When outside a bracket and inside, favor jumping to the inside bracket
7963    assert(
7964        "console.log('foo', [1, 2, 3]ˇ);",
7965        "console.log(ˇ'foo', [1, 2, 3]);",
7966    );
7967    assert(
7968        "console.log(ˇ'foo', [1, 2, 3]);",
7969        "console.log('foo', [1, 2, 3]ˇ);",
7970    );
7971
7972    // Bias forward if two options are equally likely
7973    assert(
7974        "let result = curried_fun()ˇ();",
7975        "let result = curried_fun()()ˇ;",
7976    );
7977
7978    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
7979    assert(
7980        indoc! {"
7981            function test() {
7982                console.log('test')ˇ
7983            }"},
7984        indoc! {"
7985            function test() {
7986                console.logˇ('test')
7987            }"},
7988    );
7989}
7990
7991#[gpui::test]
7992async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
7993    init_test(cx, |_| {});
7994
7995    let fs = FakeFs::new(cx.executor());
7996    fs.insert_tree(
7997        "/a",
7998        json!({
7999            "main.rs": "fn main() { let a = 5; }",
8000            "other.rs": "// Test file",
8001        }),
8002    )
8003    .await;
8004    let project = Project::test(fs, ["/a".as_ref()], cx).await;
8005
8006    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8007    language_registry.add(Arc::new(Language::new(
8008        LanguageConfig {
8009            name: "Rust".into(),
8010            matcher: LanguageMatcher {
8011                path_suffixes: vec!["rs".to_string()],
8012                ..Default::default()
8013            },
8014            brackets: BracketPairConfig {
8015                pairs: vec![BracketPair {
8016                    start: "{".to_string(),
8017                    end: "}".to_string(),
8018                    close: true,
8019                    newline: true,
8020                }],
8021                disabled_scopes_by_bracket_ix: Vec::new(),
8022            },
8023            ..Default::default()
8024        },
8025        Some(tree_sitter_rust::language()),
8026    )));
8027    let mut fake_servers = language_registry.register_fake_lsp_adapter(
8028        "Rust",
8029        FakeLspAdapter {
8030            capabilities: lsp::ServerCapabilities {
8031                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
8032                    first_trigger_character: "{".to_string(),
8033                    more_trigger_character: None,
8034                }),
8035                ..Default::default()
8036            },
8037            ..Default::default()
8038        },
8039    );
8040
8041    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8042
8043    let cx = &mut VisualTestContext::from_window(*workspace, cx);
8044
8045    let worktree_id = workspace
8046        .update(cx, |workspace, cx| {
8047            workspace.project().update(cx, |project, cx| {
8048                project.worktrees().next().unwrap().read(cx).id()
8049            })
8050        })
8051        .unwrap();
8052
8053    let buffer = project
8054        .update(cx, |project, cx| {
8055            project.open_local_buffer("/a/main.rs", cx)
8056        })
8057        .await
8058        .unwrap();
8059    cx.executor().run_until_parked();
8060    cx.executor().start_waiting();
8061    let fake_server = fake_servers.next().await.unwrap();
8062    let editor_handle = workspace
8063        .update(cx, |workspace, cx| {
8064            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
8065        })
8066        .unwrap()
8067        .await
8068        .unwrap()
8069        .downcast::<Editor>()
8070        .unwrap();
8071
8072    fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
8073        assert_eq!(
8074            params.text_document_position.text_document.uri,
8075            lsp::Url::from_file_path("/a/main.rs").unwrap(),
8076        );
8077        assert_eq!(
8078            params.text_document_position.position,
8079            lsp::Position::new(0, 21),
8080        );
8081
8082        Ok(Some(vec![lsp::TextEdit {
8083            new_text: "]".to_string(),
8084            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
8085        }]))
8086    });
8087
8088    editor_handle.update(cx, |editor, cx| {
8089        editor.focus(cx);
8090        editor.change_selections(None, cx, |s| {
8091            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
8092        });
8093        editor.handle_input("{", cx);
8094    });
8095
8096    cx.executor().run_until_parked();
8097
8098    _ = buffer.update(cx, |buffer, _| {
8099        assert_eq!(
8100            buffer.text(),
8101            "fn main() { let a = {5}; }",
8102            "No extra braces from on type formatting should appear in the buffer"
8103        )
8104    });
8105}
8106
8107#[gpui::test]
8108async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
8109    init_test(cx, |_| {});
8110
8111    let fs = FakeFs::new(cx.executor());
8112    fs.insert_tree(
8113        "/a",
8114        json!({
8115            "main.rs": "fn main() { let a = 5; }",
8116            "other.rs": "// Test file",
8117        }),
8118    )
8119    .await;
8120
8121    let project = Project::test(fs, ["/a".as_ref()], cx).await;
8122
8123    let server_restarts = Arc::new(AtomicUsize::new(0));
8124    let closure_restarts = Arc::clone(&server_restarts);
8125    let language_server_name = "test language server";
8126    let language_name: Arc<str> = "Rust".into();
8127
8128    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8129    language_registry.add(Arc::new(Language::new(
8130        LanguageConfig {
8131            name: Arc::clone(&language_name),
8132            matcher: LanguageMatcher {
8133                path_suffixes: vec!["rs".to_string()],
8134                ..Default::default()
8135            },
8136            ..Default::default()
8137        },
8138        Some(tree_sitter_rust::language()),
8139    )));
8140    let mut fake_servers = language_registry.register_fake_lsp_adapter(
8141        "Rust",
8142        FakeLspAdapter {
8143            name: language_server_name,
8144            initialization_options: Some(json!({
8145                "testOptionValue": true
8146            })),
8147            initializer: Some(Box::new(move |fake_server| {
8148                let task_restarts = Arc::clone(&closure_restarts);
8149                fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
8150                    task_restarts.fetch_add(1, atomic::Ordering::Release);
8151                    futures::future::ready(Ok(()))
8152                });
8153            })),
8154            ..Default::default()
8155        },
8156    );
8157
8158    let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8159    let _buffer = project
8160        .update(cx, |project, cx| {
8161            project.open_local_buffer("/a/main.rs", cx)
8162        })
8163        .await
8164        .unwrap();
8165    let _fake_server = fake_servers.next().await.unwrap();
8166    update_test_language_settings(cx, |language_settings| {
8167        language_settings.languages.insert(
8168            Arc::clone(&language_name),
8169            LanguageSettingsContent {
8170                tab_size: NonZeroU32::new(8),
8171                ..Default::default()
8172            },
8173        );
8174    });
8175    cx.executor().run_until_parked();
8176    assert_eq!(
8177        server_restarts.load(atomic::Ordering::Acquire),
8178        0,
8179        "Should not restart LSP server on an unrelated change"
8180    );
8181
8182    update_test_project_settings(cx, |project_settings| {
8183        project_settings.lsp.insert(
8184            "Some other server name".into(),
8185            LspSettings {
8186                binary: None,
8187                settings: None,
8188                initialization_options: Some(json!({
8189                    "some other init value": false
8190                })),
8191            },
8192        );
8193    });
8194    cx.executor().run_until_parked();
8195    assert_eq!(
8196        server_restarts.load(atomic::Ordering::Acquire),
8197        0,
8198        "Should not restart LSP server on an unrelated LSP settings change"
8199    );
8200
8201    update_test_project_settings(cx, |project_settings| {
8202        project_settings.lsp.insert(
8203            language_server_name.into(),
8204            LspSettings {
8205                binary: None,
8206                settings: None,
8207                initialization_options: Some(json!({
8208                    "anotherInitValue": false
8209                })),
8210            },
8211        );
8212    });
8213    cx.executor().run_until_parked();
8214    assert_eq!(
8215        server_restarts.load(atomic::Ordering::Acquire),
8216        1,
8217        "Should restart LSP server on a related LSP settings change"
8218    );
8219
8220    update_test_project_settings(cx, |project_settings| {
8221        project_settings.lsp.insert(
8222            language_server_name.into(),
8223            LspSettings {
8224                binary: None,
8225                settings: None,
8226                initialization_options: Some(json!({
8227                    "anotherInitValue": false
8228                })),
8229            },
8230        );
8231    });
8232    cx.executor().run_until_parked();
8233    assert_eq!(
8234        server_restarts.load(atomic::Ordering::Acquire),
8235        1,
8236        "Should not restart LSP server on a related LSP settings change that is the same"
8237    );
8238
8239    update_test_project_settings(cx, |project_settings| {
8240        project_settings.lsp.insert(
8241            language_server_name.into(),
8242            LspSettings {
8243                binary: None,
8244                settings: None,
8245                initialization_options: None,
8246            },
8247        );
8248    });
8249    cx.executor().run_until_parked();
8250    assert_eq!(
8251        server_restarts.load(atomic::Ordering::Acquire),
8252        2,
8253        "Should restart LSP server on another related LSP settings change"
8254    );
8255}
8256
8257#[gpui::test]
8258async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
8259    init_test(cx, |_| {});
8260
8261    let mut cx = EditorLspTestContext::new_rust(
8262        lsp::ServerCapabilities {
8263            completion_provider: Some(lsp::CompletionOptions {
8264                trigger_characters: Some(vec![".".to_string()]),
8265                resolve_provider: Some(true),
8266                ..Default::default()
8267            }),
8268            ..Default::default()
8269        },
8270        cx,
8271    )
8272    .await;
8273
8274    cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8275    cx.simulate_keystroke(".");
8276    let completion_item = lsp::CompletionItem {
8277        label: "some".into(),
8278        kind: Some(lsp::CompletionItemKind::SNIPPET),
8279        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8280        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8281            kind: lsp::MarkupKind::Markdown,
8282            value: "```rust\nSome(2)\n```".to_string(),
8283        })),
8284        deprecated: Some(false),
8285        sort_text: Some("fffffff2".to_string()),
8286        filter_text: Some("some".to_string()),
8287        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8288        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8289            range: lsp::Range {
8290                start: lsp::Position {
8291                    line: 0,
8292                    character: 22,
8293                },
8294                end: lsp::Position {
8295                    line: 0,
8296                    character: 22,
8297                },
8298            },
8299            new_text: "Some(2)".to_string(),
8300        })),
8301        additional_text_edits: Some(vec![lsp::TextEdit {
8302            range: lsp::Range {
8303                start: lsp::Position {
8304                    line: 0,
8305                    character: 20,
8306                },
8307                end: lsp::Position {
8308                    line: 0,
8309                    character: 22,
8310                },
8311            },
8312            new_text: "".to_string(),
8313        }]),
8314        ..Default::default()
8315    };
8316
8317    let closure_completion_item = completion_item.clone();
8318    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8319        let task_completion_item = closure_completion_item.clone();
8320        async move {
8321            Ok(Some(lsp::CompletionResponse::Array(vec![
8322                task_completion_item,
8323            ])))
8324        }
8325    });
8326
8327    request.next().await;
8328
8329    cx.condition(|editor, _| editor.context_menu_visible())
8330        .await;
8331    let apply_additional_edits = cx.update_editor(|editor, cx| {
8332        editor
8333            .confirm_completion(&ConfirmCompletion::default(), cx)
8334            .unwrap()
8335    });
8336    cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
8337
8338    cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
8339        let task_completion_item = completion_item.clone();
8340        async move { Ok(task_completion_item) }
8341    })
8342    .next()
8343    .await
8344    .unwrap();
8345    apply_additional_edits.await.unwrap();
8346    cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
8347}
8348
8349#[gpui::test]
8350async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
8351    init_test(cx, |_| {});
8352
8353    let mut cx = EditorLspTestContext::new(
8354        Language::new(
8355            LanguageConfig {
8356                matcher: LanguageMatcher {
8357                    path_suffixes: vec!["jsx".into()],
8358                    ..Default::default()
8359                },
8360                overrides: [(
8361                    "element".into(),
8362                    LanguageConfigOverride {
8363                        word_characters: Override::Set(['-'].into_iter().collect()),
8364                        ..Default::default()
8365                    },
8366                )]
8367                .into_iter()
8368                .collect(),
8369                ..Default::default()
8370            },
8371            Some(tree_sitter_typescript::language_tsx()),
8372        )
8373        .with_override_query("(jsx_self_closing_element) @element")
8374        .unwrap(),
8375        lsp::ServerCapabilities {
8376            completion_provider: Some(lsp::CompletionOptions {
8377                trigger_characters: Some(vec![":".to_string()]),
8378                ..Default::default()
8379            }),
8380            ..Default::default()
8381        },
8382        cx,
8383    )
8384    .await;
8385
8386    cx.lsp
8387        .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8388            Ok(Some(lsp::CompletionResponse::Array(vec![
8389                lsp::CompletionItem {
8390                    label: "bg-blue".into(),
8391                    ..Default::default()
8392                },
8393                lsp::CompletionItem {
8394                    label: "bg-red".into(),
8395                    ..Default::default()
8396                },
8397                lsp::CompletionItem {
8398                    label: "bg-yellow".into(),
8399                    ..Default::default()
8400                },
8401            ])))
8402        });
8403
8404    cx.set_state(r#"<p class="bgˇ" />"#);
8405
8406    // Trigger completion when typing a dash, because the dash is an extra
8407    // word character in the 'element' scope, which contains the cursor.
8408    cx.simulate_keystroke("-");
8409    cx.executor().run_until_parked();
8410    cx.update_editor(|editor, _| {
8411        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8412            assert_eq!(
8413                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8414                &["bg-red", "bg-blue", "bg-yellow"]
8415            );
8416        } else {
8417            panic!("expected completion menu to be open");
8418        }
8419    });
8420
8421    cx.simulate_keystroke("l");
8422    cx.executor().run_until_parked();
8423    cx.update_editor(|editor, _| {
8424        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8425            assert_eq!(
8426                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8427                &["bg-blue", "bg-yellow"]
8428            );
8429        } else {
8430            panic!("expected completion menu to be open");
8431        }
8432    });
8433
8434    // When filtering completions, consider the character after the '-' to
8435    // be the start of a subword.
8436    cx.set_state(r#"<p class="yelˇ" />"#);
8437    cx.simulate_keystroke("l");
8438    cx.executor().run_until_parked();
8439    cx.update_editor(|editor, _| {
8440        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8441            assert_eq!(
8442                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8443                &["bg-yellow"]
8444            );
8445        } else {
8446            panic!("expected completion menu to be open");
8447        }
8448    });
8449}
8450
8451#[gpui::test]
8452async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
8453    init_test(cx, |settings| {
8454        settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
8455    });
8456
8457    let fs = FakeFs::new(cx.executor());
8458    fs.insert_file("/file.rs", Default::default()).await;
8459
8460    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8461    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8462
8463    language_registry.add(Arc::new(Language::new(
8464        LanguageConfig {
8465            name: "Rust".into(),
8466            matcher: LanguageMatcher {
8467                path_suffixes: vec!["rs".to_string()],
8468                ..Default::default()
8469            },
8470            prettier_parser_name: Some("test_parser".to_string()),
8471            ..Default::default()
8472        },
8473        Some(tree_sitter_rust::language()),
8474    )));
8475
8476    let test_plugin = "test_plugin";
8477    let _ = language_registry.register_fake_lsp_adapter(
8478        "Rust",
8479        FakeLspAdapter {
8480            prettier_plugins: vec![test_plugin],
8481            ..Default::default()
8482        },
8483    );
8484
8485    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
8486    let buffer = project
8487        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
8488        .await
8489        .unwrap();
8490
8491    let buffer_text = "one\ntwo\nthree\n";
8492    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
8493    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8494    _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
8495
8496    editor
8497        .update(cx, |editor, cx| {
8498            editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8499        })
8500        .unwrap()
8501        .await;
8502    assert_eq!(
8503        editor.update(cx, |editor, cx| editor.text(cx)),
8504        buffer_text.to_string() + prettier_format_suffix,
8505        "Test prettier formatting was not applied to the original buffer text",
8506    );
8507
8508    update_test_language_settings(cx, |settings| {
8509        settings.defaults.formatter = Some(language_settings::Formatter::Auto)
8510    });
8511    let format = editor.update(cx, |editor, cx| {
8512        editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8513    });
8514    format.await.unwrap();
8515    assert_eq!(
8516        editor.update(cx, |editor, cx| editor.text(cx)),
8517        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
8518        "Autoformatting (via test prettier) was not applied to the original buffer text",
8519    );
8520}
8521
8522#[gpui::test]
8523async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
8524    init_test(cx, |_| {});
8525    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
8526    let base_text = indoc! {r#"struct Row;
8527struct Row1;
8528struct Row2;
8529
8530struct Row4;
8531struct Row5;
8532struct Row6;
8533
8534struct Row8;
8535struct Row9;
8536struct Row10;"#};
8537
8538    // When addition hunks are not adjacent to carets, no hunk revert is performed
8539    assert_hunk_revert(
8540        indoc! {r#"struct Row;
8541                   struct Row1;
8542                   struct Row1.1;
8543                   struct Row1.2;
8544                   struct Row2;ˇ
8545
8546                   struct Row4;
8547                   struct Row5;
8548                   struct Row6;
8549
8550                   struct Row8;
8551                   ˇstruct Row9;
8552                   struct Row9.1;
8553                   struct Row9.2;
8554                   struct Row9.3;
8555                   struct Row10;"#},
8556        vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
8557        indoc! {r#"struct Row;
8558                   struct Row1;
8559                   struct Row1.1;
8560                   struct Row1.2;
8561                   struct Row2;ˇ
8562
8563                   struct Row4;
8564                   struct Row5;
8565                   struct Row6;
8566
8567                   struct Row8;
8568                   ˇstruct Row9;
8569                   struct Row9.1;
8570                   struct Row9.2;
8571                   struct Row9.3;
8572                   struct Row10;"#},
8573        base_text,
8574        &mut cx,
8575    );
8576    // Same for selections
8577    assert_hunk_revert(
8578        indoc! {r#"struct Row;
8579                   struct Row1;
8580                   struct Row2;
8581                   struct Row2.1;
8582                   struct Row2.2;
8583                   «ˇ
8584                   struct Row4;
8585                   struct» Row5;
8586                   «struct Row6;
8587                   ˇ»
8588                   struct Row9.1;
8589                   struct Row9.2;
8590                   struct Row9.3;
8591                   struct Row8;
8592                   struct Row9;
8593                   struct Row10;"#},
8594        vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
8595        indoc! {r#"struct Row;
8596                   struct Row1;
8597                   struct Row2;
8598                   struct Row2.1;
8599                   struct Row2.2;
8600                   «ˇ
8601                   struct Row4;
8602                   struct» Row5;
8603                   «struct Row6;
8604                   ˇ»
8605                   struct Row9.1;
8606                   struct Row9.2;
8607                   struct Row9.3;
8608                   struct Row8;
8609                   struct Row9;
8610                   struct Row10;"#},
8611        base_text,
8612        &mut cx,
8613    );
8614
8615    // When carets and selections intersect the addition hunks, those are reverted.
8616    // Adjacent carets got merged.
8617    assert_hunk_revert(
8618        indoc! {r#"struct Row;
8619                   ˇ// something on the top
8620                   struct Row1;
8621                   struct Row2;
8622                   struct Roˇw3.1;
8623                   struct Row2.2;
8624                   struct Row2.3;ˇ
8625
8626                   struct Row4;
8627                   struct ˇRow5.1;
8628                   struct Row5.2;
8629                   struct «Rowˇ»5.3;
8630                   struct Row5;
8631                   struct Row6;
8632                   ˇ
8633                   struct Row9.1;
8634                   struct «Rowˇ»9.2;
8635                   struct «ˇRow»9.3;
8636                   struct Row8;
8637                   struct Row9;
8638                   «ˇ// something on bottom»
8639                   struct Row10;"#},
8640        vec![
8641            DiffHunkStatus::Added,
8642            DiffHunkStatus::Added,
8643            DiffHunkStatus::Added,
8644            DiffHunkStatus::Added,
8645            DiffHunkStatus::Added,
8646        ],
8647        indoc! {r#"struct Row;
8648                   ˇstruct Row1;
8649                   struct Row2;
8650                   ˇ
8651                   struct Row4;
8652                   ˇstruct Row5;
8653                   struct Row6;
8654                   ˇ
8655                   ˇstruct Row8;
8656                   struct Row9;
8657                   ˇstruct Row10;"#},
8658        base_text,
8659        &mut cx,
8660    );
8661}
8662
8663#[gpui::test]
8664async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
8665    init_test(cx, |_| {});
8666    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
8667    let base_text = indoc! {r#"struct Row;
8668struct Row1;
8669struct Row2;
8670
8671struct Row4;
8672struct Row5;
8673struct Row6;
8674
8675struct Row8;
8676struct Row9;
8677struct Row10;"#};
8678
8679    // Modification hunks behave the same as the addition ones.
8680    assert_hunk_revert(
8681        indoc! {r#"struct Row;
8682                   struct Row1;
8683                   struct Row33;
8684                   ˇ
8685                   struct Row4;
8686                   struct Row5;
8687                   struct Row6;
8688                   ˇ
8689                   struct Row99;
8690                   struct Row9;
8691                   struct Row10;"#},
8692        vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
8693        indoc! {r#"struct Row;
8694                   struct Row1;
8695                   struct Row33;
8696                   ˇ
8697                   struct Row4;
8698                   struct Row5;
8699                   struct Row6;
8700                   ˇ
8701                   struct Row99;
8702                   struct Row9;
8703                   struct Row10;"#},
8704        base_text,
8705        &mut cx,
8706    );
8707    assert_hunk_revert(
8708        indoc! {r#"struct Row;
8709                   struct Row1;
8710                   struct Row33;
8711                   «ˇ
8712                   struct Row4;
8713                   struct» Row5;
8714                   «struct Row6;
8715                   ˇ»
8716                   struct Row99;
8717                   struct Row9;
8718                   struct Row10;"#},
8719        vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
8720        indoc! {r#"struct Row;
8721                   struct Row1;
8722                   struct Row33;
8723                   «ˇ
8724                   struct Row4;
8725                   struct» Row5;
8726                   «struct Row6;
8727                   ˇ»
8728                   struct Row99;
8729                   struct Row9;
8730                   struct Row10;"#},
8731        base_text,
8732        &mut cx,
8733    );
8734
8735    assert_hunk_revert(
8736        indoc! {r#"ˇstruct Row1.1;
8737                   struct Row1;
8738                   «ˇstr»uct Row22;
8739
8740                   struct ˇRow44;
8741                   struct Row5;
8742                   struct «Rˇ»ow66;ˇ
8743
8744                   «struˇ»ct Row88;
8745                   struct Row9;
8746                   struct Row1011;ˇ"#},
8747        vec![
8748            DiffHunkStatus::Modified,
8749            DiffHunkStatus::Modified,
8750            DiffHunkStatus::Modified,
8751            DiffHunkStatus::Modified,
8752            DiffHunkStatus::Modified,
8753            DiffHunkStatus::Modified,
8754        ],
8755        indoc! {r#"struct Row;
8756                   ˇstruct Row1;
8757                   struct Row2;
8758                   ˇ
8759                   struct Row4;
8760                   ˇstruct Row5;
8761                   struct Row6;
8762                   ˇ
8763                   struct Row8;
8764                   ˇstruct Row9;
8765                   struct Row10;ˇ"#},
8766        base_text,
8767        &mut cx,
8768    );
8769}
8770
8771#[gpui::test]
8772async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
8773    init_test(cx, |_| {});
8774    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
8775    let base_text = indoc! {r#"struct Row;
8776struct Row1;
8777struct Row2;
8778
8779struct Row4;
8780struct Row5;
8781struct Row6;
8782
8783struct Row8;
8784struct Row9;
8785struct Row10;"#};
8786
8787    // Deletion hunks trigger with carets on ajacent rows, so carets and selections have to stay farther to avoid the revert
8788    assert_hunk_revert(
8789        indoc! {r#"struct Row;
8790                   struct Row2;
8791
8792                   ˇstruct Row4;
8793                   struct Row5;
8794                   struct Row6;
8795                   ˇ
8796                   struct Row8;
8797                   struct Row10;"#},
8798        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
8799        indoc! {r#"struct Row;
8800                   struct Row2;
8801
8802                   ˇstruct Row4;
8803                   struct Row5;
8804                   struct Row6;
8805                   ˇ
8806                   struct Row8;
8807                   struct Row10;"#},
8808        base_text,
8809        &mut cx,
8810    );
8811    assert_hunk_revert(
8812        indoc! {r#"struct Row;
8813                   struct Row2;
8814
8815                   «ˇstruct Row4;
8816                   struct» Row5;
8817                   «struct Row6;
8818                   ˇ»
8819                   struct Row8;
8820                   struct Row10;"#},
8821        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
8822        indoc! {r#"struct Row;
8823                   struct Row2;
8824
8825                   «ˇstruct Row4;
8826                   struct» Row5;
8827                   «struct Row6;
8828                   ˇ»
8829                   struct Row8;
8830                   struct Row10;"#},
8831        base_text,
8832        &mut cx,
8833    );
8834
8835    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
8836    assert_hunk_revert(
8837        indoc! {r#"struct Row;
8838                   ˇstruct Row2;
8839
8840                   struct Row4;
8841                   struct Row5;
8842                   struct Row6;
8843
8844                   struct Row8;ˇ
8845                   struct Row10;"#},
8846        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
8847        indoc! {r#"struct Row;
8848                   struct Row1;
8849                   ˇstruct Row2;
8850
8851                   struct Row4;
8852                   struct Row5;
8853                   struct Row6;
8854
8855                   struct Row8;ˇ
8856                   struct Row9;
8857                   struct Row10;"#},
8858        base_text,
8859        &mut cx,
8860    );
8861    assert_hunk_revert(
8862        indoc! {r#"struct Row;
8863                   struct Row2«ˇ;
8864                   struct Row4;
8865                   struct» Row5;
8866                   «struct Row6;
8867
8868                   struct Row8;ˇ»
8869                   struct Row10;"#},
8870        vec![
8871            DiffHunkStatus::Removed,
8872            DiffHunkStatus::Removed,
8873            DiffHunkStatus::Removed,
8874        ],
8875        indoc! {r#"struct Row;
8876                   struct Row1;
8877                   struct Row2«ˇ;
8878
8879                   struct Row4;
8880                   struct» Row5;
8881                   «struct Row6;
8882
8883                   struct Row8;ˇ»
8884                   struct Row9;
8885                   struct Row10;"#},
8886        base_text,
8887        &mut cx,
8888    );
8889}
8890
8891#[gpui::test]
8892async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
8893    init_test(cx, |_| {});
8894
8895    let cols = 4;
8896    let rows = 10;
8897    let sample_text_1 = sample_text(rows, cols, 'a');
8898    assert_eq!(
8899        sample_text_1,
8900        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
8901    );
8902    let sample_text_2 = sample_text(rows, cols, 'l');
8903    assert_eq!(
8904        sample_text_2,
8905        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
8906    );
8907    let sample_text_3 = sample_text(rows, cols, 'v');
8908    assert_eq!(
8909        sample_text_3,
8910        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
8911    );
8912
8913    fn diff_every_buffer_row(
8914        buffer: &Model<Buffer>,
8915        sample_text: String,
8916        cols: usize,
8917        cx: &mut gpui::TestAppContext,
8918    ) {
8919        // revert first character in each row, creating one large diff hunk per buffer
8920        let is_first_char = |offset: usize| offset % cols == 0;
8921        buffer.update(cx, |buffer, cx| {
8922            buffer.set_text(
8923                sample_text
8924                    .chars()
8925                    .enumerate()
8926                    .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
8927                    .collect::<String>(),
8928                cx,
8929            );
8930            buffer.set_diff_base(Some(sample_text), cx);
8931        });
8932        cx.executor().run_until_parked();
8933    }
8934
8935    let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
8936    diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
8937
8938    let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
8939    diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
8940
8941    let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
8942    diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
8943
8944    let multibuffer = cx.new_model(|cx| {
8945        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8946        multibuffer.push_excerpts(
8947            buffer_1.clone(),
8948            [
8949                ExcerptRange {
8950                    context: Point::new(0, 0)..Point::new(3, 0),
8951                    primary: None,
8952                },
8953                ExcerptRange {
8954                    context: Point::new(5, 0)..Point::new(7, 0),
8955                    primary: None,
8956                },
8957                ExcerptRange {
8958                    context: Point::new(9, 0)..Point::new(10, 4),
8959                    primary: None,
8960                },
8961            ],
8962            cx,
8963        );
8964        multibuffer.push_excerpts(
8965            buffer_2.clone(),
8966            [
8967                ExcerptRange {
8968                    context: Point::new(0, 0)..Point::new(3, 0),
8969                    primary: None,
8970                },
8971                ExcerptRange {
8972                    context: Point::new(5, 0)..Point::new(7, 0),
8973                    primary: None,
8974                },
8975                ExcerptRange {
8976                    context: Point::new(9, 0)..Point::new(10, 4),
8977                    primary: None,
8978                },
8979            ],
8980            cx,
8981        );
8982        multibuffer.push_excerpts(
8983            buffer_3.clone(),
8984            [
8985                ExcerptRange {
8986                    context: Point::new(0, 0)..Point::new(3, 0),
8987                    primary: None,
8988                },
8989                ExcerptRange {
8990                    context: Point::new(5, 0)..Point::new(7, 0),
8991                    primary: None,
8992                },
8993                ExcerptRange {
8994                    context: Point::new(9, 0)..Point::new(10, 4),
8995                    primary: None,
8996                },
8997            ],
8998            cx,
8999        );
9000        multibuffer
9001    });
9002
9003    let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9004    editor.update(cx, |editor, cx| {
9005        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");
9006        editor.select_all(&SelectAll, cx);
9007        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
9008    });
9009    cx.executor().run_until_parked();
9010    // When all ranges are selected, all buffer hunks are reverted.
9011    editor.update(cx, |editor, cx| {
9012        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");
9013    });
9014    buffer_1.update(cx, |buffer, _| {
9015        assert_eq!(buffer.text(), sample_text_1);
9016    });
9017    buffer_2.update(cx, |buffer, _| {
9018        assert_eq!(buffer.text(), sample_text_2);
9019    });
9020    buffer_3.update(cx, |buffer, _| {
9021        assert_eq!(buffer.text(), sample_text_3);
9022    });
9023
9024    diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
9025    diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
9026    diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
9027    editor.update(cx, |editor, cx| {
9028        editor.change_selections(None, cx, |s| {
9029            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
9030        });
9031        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
9032    });
9033    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
9034    // but not affect buffer_2 and its related excerpts.
9035    editor.update(cx, |editor, cx| {
9036        assert_eq!(
9037            editor.text(cx),
9038            "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"
9039        );
9040    });
9041    buffer_1.update(cx, |buffer, _| {
9042        assert_eq!(buffer.text(), sample_text_1);
9043    });
9044    buffer_2.update(cx, |buffer, _| {
9045        assert_eq!(
9046            buffer.text(),
9047            "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
9048        );
9049    });
9050    buffer_3.update(cx, |buffer, _| {
9051        assert_eq!(
9052            buffer.text(),
9053            "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
9054        );
9055    });
9056}
9057
9058#[gpui::test]
9059async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
9060    init_test(cx, |_| {});
9061
9062    let cols = 4;
9063    let rows = 10;
9064    let sample_text_1 = sample_text(rows, cols, 'a');
9065    assert_eq!(
9066        sample_text_1,
9067        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
9068    );
9069    let sample_text_2 = sample_text(rows, cols, 'l');
9070    assert_eq!(
9071        sample_text_2,
9072        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
9073    );
9074    let sample_text_3 = sample_text(rows, cols, 'v');
9075    assert_eq!(
9076        sample_text_3,
9077        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
9078    );
9079
9080    let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
9081    let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
9082    let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
9083
9084    let multi_buffer = cx.new_model(|cx| {
9085        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
9086        multibuffer.push_excerpts(
9087            buffer_1.clone(),
9088            [
9089                ExcerptRange {
9090                    context: Point::new(0, 0)..Point::new(3, 0),
9091                    primary: None,
9092                },
9093                ExcerptRange {
9094                    context: Point::new(5, 0)..Point::new(7, 0),
9095                    primary: None,
9096                },
9097                ExcerptRange {
9098                    context: Point::new(9, 0)..Point::new(10, 4),
9099                    primary: None,
9100                },
9101            ],
9102            cx,
9103        );
9104        multibuffer.push_excerpts(
9105            buffer_2.clone(),
9106            [
9107                ExcerptRange {
9108                    context: Point::new(0, 0)..Point::new(3, 0),
9109                    primary: None,
9110                },
9111                ExcerptRange {
9112                    context: Point::new(5, 0)..Point::new(7, 0),
9113                    primary: None,
9114                },
9115                ExcerptRange {
9116                    context: Point::new(9, 0)..Point::new(10, 4),
9117                    primary: None,
9118                },
9119            ],
9120            cx,
9121        );
9122        multibuffer.push_excerpts(
9123            buffer_3.clone(),
9124            [
9125                ExcerptRange {
9126                    context: Point::new(0, 0)..Point::new(3, 0),
9127                    primary: None,
9128                },
9129                ExcerptRange {
9130                    context: Point::new(5, 0)..Point::new(7, 0),
9131                    primary: None,
9132                },
9133                ExcerptRange {
9134                    context: Point::new(9, 0)..Point::new(10, 4),
9135                    primary: None,
9136                },
9137            ],
9138            cx,
9139        );
9140        multibuffer
9141    });
9142
9143    let fs = FakeFs::new(cx.executor());
9144    fs.insert_tree(
9145        "/a",
9146        json!({
9147            "main.rs": sample_text_1,
9148            "other.rs": sample_text_2,
9149            "lib.rs": sample_text_3,
9150        }),
9151    )
9152    .await;
9153    let project = Project::test(fs, ["/a".as_ref()], cx).await;
9154    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9155    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9156    let multi_buffer_editor =
9157        cx.new_view(|cx| Editor::new(EditorMode::Full, multi_buffer, Some(project.clone()), cx));
9158    let multibuffer_item_id = workspace
9159        .update(cx, |workspace, cx| {
9160            assert!(
9161                workspace.active_item(cx).is_none(),
9162                "active item should be None before the first item is added"
9163            );
9164            workspace.add_item_to_active_pane(Box::new(multi_buffer_editor.clone()), cx);
9165            let active_item = workspace
9166                .active_item(cx)
9167                .expect("should have an active item after adding the multi buffer");
9168            assert!(
9169                !active_item.is_singleton(cx),
9170                "A multi buffer was expected to active after adding"
9171            );
9172            active_item.item_id()
9173        })
9174        .unwrap();
9175    cx.executor().run_until_parked();
9176
9177    multi_buffer_editor.update(cx, |editor, cx| {
9178        editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
9179        editor.open_excerpts(&OpenExcerpts, cx);
9180    });
9181    cx.executor().run_until_parked();
9182    let first_item_id = workspace
9183        .update(cx, |workspace, cx| {
9184            let active_item = workspace
9185                .active_item(cx)
9186                .expect("should have an active item after navigating into the 1st buffer");
9187            let first_item_id = active_item.item_id();
9188            assert_ne!(
9189                first_item_id, multibuffer_item_id,
9190                "Should navigate into the 1st buffer and activate it"
9191            );
9192            assert!(
9193                active_item.is_singleton(cx),
9194                "New active item should be a singleton buffer"
9195            );
9196            assert_eq!(
9197                active_item
9198                    .act_as::<Editor>(cx)
9199                    .expect("should have navigated into an editor for the 1st buffer")
9200                    .read(cx)
9201                    .text(cx),
9202                sample_text_1
9203            );
9204
9205            workspace
9206                .go_back(workspace.active_pane().downgrade(), cx)
9207                .detach_and_log_err(cx);
9208
9209            first_item_id
9210        })
9211        .unwrap();
9212    cx.executor().run_until_parked();
9213    workspace
9214        .update(cx, |workspace, cx| {
9215            let active_item = workspace
9216                .active_item(cx)
9217                .expect("should have an active item after navigating back");
9218            assert_eq!(
9219                active_item.item_id(),
9220                multibuffer_item_id,
9221                "Should navigate back to the multi buffer"
9222            );
9223            assert!(!active_item.is_singleton(cx));
9224        })
9225        .unwrap();
9226
9227    multi_buffer_editor.update(cx, |editor, cx| {
9228        editor.change_selections(Some(Autoscroll::Next), cx, |s| {
9229            s.select_ranges(Some(39..40))
9230        });
9231        editor.open_excerpts(&OpenExcerpts, cx);
9232    });
9233    cx.executor().run_until_parked();
9234    let second_item_id = workspace
9235        .update(cx, |workspace, cx| {
9236            let active_item = workspace
9237                .active_item(cx)
9238                .expect("should have an active item after navigating into the 2nd buffer");
9239            let second_item_id = active_item.item_id();
9240            assert_ne!(
9241                second_item_id, multibuffer_item_id,
9242                "Should navigate away from the multibuffer"
9243            );
9244            assert_ne!(
9245                second_item_id, first_item_id,
9246                "Should navigate into the 2nd buffer and activate it"
9247            );
9248            assert!(
9249                active_item.is_singleton(cx),
9250                "New active item should be a singleton buffer"
9251            );
9252            assert_eq!(
9253                active_item
9254                    .act_as::<Editor>(cx)
9255                    .expect("should have navigated into an editor")
9256                    .read(cx)
9257                    .text(cx),
9258                sample_text_2
9259            );
9260
9261            workspace
9262                .go_back(workspace.active_pane().downgrade(), cx)
9263                .detach_and_log_err(cx);
9264
9265            second_item_id
9266        })
9267        .unwrap();
9268    cx.executor().run_until_parked();
9269    workspace
9270        .update(cx, |workspace, cx| {
9271            let active_item = workspace
9272                .active_item(cx)
9273                .expect("should have an active item after navigating back from the 2nd buffer");
9274            assert_eq!(
9275                active_item.item_id(),
9276                multibuffer_item_id,
9277                "Should navigate back from the 2nd buffer to the multi buffer"
9278            );
9279            assert!(!active_item.is_singleton(cx));
9280        })
9281        .unwrap();
9282
9283    multi_buffer_editor.update(cx, |editor, cx| {
9284        editor.change_selections(Some(Autoscroll::Next), cx, |s| {
9285            s.select_ranges(Some(60..70))
9286        });
9287        editor.open_excerpts(&OpenExcerpts, cx);
9288    });
9289    cx.executor().run_until_parked();
9290    workspace
9291        .update(cx, |workspace, cx| {
9292            let active_item = workspace
9293                .active_item(cx)
9294                .expect("should have an active item after navigating into the 3rd buffer");
9295            let third_item_id = active_item.item_id();
9296            assert_ne!(
9297                third_item_id, multibuffer_item_id,
9298                "Should navigate into the 3rd buffer and activate it"
9299            );
9300            assert_ne!(third_item_id, first_item_id);
9301            assert_ne!(third_item_id, second_item_id);
9302            assert!(
9303                active_item.is_singleton(cx),
9304                "New active item should be a singleton buffer"
9305            );
9306            assert_eq!(
9307                active_item
9308                    .act_as::<Editor>(cx)
9309                    .expect("should have navigated into an editor")
9310                    .read(cx)
9311                    .text(cx),
9312                sample_text_3
9313            );
9314
9315            workspace
9316                .go_back(workspace.active_pane().downgrade(), cx)
9317                .detach_and_log_err(cx);
9318        })
9319        .unwrap();
9320    cx.executor().run_until_parked();
9321    workspace
9322        .update(cx, |workspace, cx| {
9323            let active_item = workspace
9324                .active_item(cx)
9325                .expect("should have an active item after navigating back from the 3rd buffer");
9326            assert_eq!(
9327                active_item.item_id(),
9328                multibuffer_item_id,
9329                "Should navigate back from the 3rd buffer to the multi buffer"
9330            );
9331            assert!(!active_item.is_singleton(cx));
9332        })
9333        .unwrap();
9334}
9335
9336fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
9337    let point = DisplayPoint::new(row as u32, column as u32);
9338    point..point
9339}
9340
9341fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
9342    let (text, ranges) = marked_text_ranges(marked_text, true);
9343    assert_eq!(view.text(cx), text);
9344    assert_eq!(
9345        view.selections.ranges(cx),
9346        ranges,
9347        "Assert selections are {}",
9348        marked_text
9349    );
9350}
9351
9352/// Handle completion request passing a marked string specifying where the completion
9353/// should be triggered from using '|' character, what range should be replaced, and what completions
9354/// should be returned using '<' and '>' to delimit the range
9355pub fn handle_completion_request(
9356    cx: &mut EditorLspTestContext,
9357    marked_string: &str,
9358    completions: Vec<&'static str>,
9359) -> impl Future<Output = ()> {
9360    let complete_from_marker: TextRangeMarker = '|'.into();
9361    let replace_range_marker: TextRangeMarker = ('<', '>').into();
9362    let (_, mut marked_ranges) = marked_text_ranges_by(
9363        marked_string,
9364        vec![complete_from_marker.clone(), replace_range_marker.clone()],
9365    );
9366
9367    let complete_from_position =
9368        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
9369    let replace_range =
9370        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
9371
9372    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
9373        let completions = completions.clone();
9374        async move {
9375            assert_eq!(params.text_document_position.text_document.uri, url.clone());
9376            assert_eq!(
9377                params.text_document_position.position,
9378                complete_from_position
9379            );
9380            Ok(Some(lsp::CompletionResponse::Array(
9381                completions
9382                    .iter()
9383                    .map(|completion_text| lsp::CompletionItem {
9384                        label: completion_text.to_string(),
9385                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9386                            range: replace_range,
9387                            new_text: completion_text.to_string(),
9388                        })),
9389                        ..Default::default()
9390                    })
9391                    .collect(),
9392            )))
9393        }
9394    });
9395
9396    async move {
9397        request.next().await;
9398    }
9399}
9400
9401fn handle_resolve_completion_request(
9402    cx: &mut EditorLspTestContext,
9403    edits: Option<Vec<(&'static str, &'static str)>>,
9404) -> impl Future<Output = ()> {
9405    let edits = edits.map(|edits| {
9406        edits
9407            .iter()
9408            .map(|(marked_string, new_text)| {
9409                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
9410                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
9411                lsp::TextEdit::new(replace_range, new_text.to_string())
9412            })
9413            .collect::<Vec<_>>()
9414    });
9415
9416    let mut request =
9417        cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
9418            let edits = edits.clone();
9419            async move {
9420                Ok(lsp::CompletionItem {
9421                    additional_text_edits: edits,
9422                    ..Default::default()
9423                })
9424            }
9425        });
9426
9427    async move {
9428        request.next().await;
9429    }
9430}
9431
9432pub(crate) fn update_test_language_settings(
9433    cx: &mut TestAppContext,
9434    f: impl Fn(&mut AllLanguageSettingsContent),
9435) {
9436    _ = cx.update(|cx| {
9437        cx.update_global(|store: &mut SettingsStore, cx| {
9438            store.update_user_settings::<AllLanguageSettings>(cx, f);
9439        });
9440    });
9441}
9442
9443pub(crate) fn update_test_project_settings(
9444    cx: &mut TestAppContext,
9445    f: impl Fn(&mut ProjectSettings),
9446) {
9447    _ = cx.update(|cx| {
9448        cx.update_global(|store: &mut SettingsStore, cx| {
9449            store.update_user_settings::<ProjectSettings>(cx, f);
9450        });
9451    });
9452}
9453
9454pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
9455    _ = cx.update(|cx| {
9456        let store = SettingsStore::test(cx);
9457        cx.set_global(store);
9458        theme::init(theme::LoadThemes::JustBase, cx);
9459        release_channel::init("0.0.0", cx);
9460        client::init_settings(cx);
9461        language::init(cx);
9462        Project::init_settings(cx);
9463        workspace::init_settings(cx);
9464        crate::init(cx);
9465    });
9466
9467    update_test_language_settings(cx, f);
9468}
9469
9470pub(crate) fn rust_lang() -> Arc<Language> {
9471    Arc::new(Language::new(
9472        LanguageConfig {
9473            name: "Rust".into(),
9474            matcher: LanguageMatcher {
9475                path_suffixes: vec!["rs".to_string()],
9476                ..Default::default()
9477            },
9478            ..Default::default()
9479        },
9480        Some(tree_sitter_rust::language()),
9481    ))
9482}
9483
9484#[track_caller]
9485fn assert_hunk_revert(
9486    not_reverted_text_with_selections: &str,
9487    expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
9488    expected_reverted_text_with_selections: &str,
9489    base_text: &str,
9490    cx: &mut EditorLspTestContext,
9491) {
9492    cx.set_state(not_reverted_text_with_selections);
9493    cx.update_editor(|editor, cx| {
9494        editor
9495            .buffer()
9496            .read(cx)
9497            .as_singleton()
9498            .unwrap()
9499            .update(cx, |buffer, cx| {
9500                buffer.set_diff_base(Some(base_text.to_string()), cx);
9501            });
9502    });
9503    cx.executor().run_until_parked();
9504
9505    let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
9506        let snapshot = editor
9507            .buffer()
9508            .read(cx)
9509            .as_singleton()
9510            .unwrap()
9511            .read(cx)
9512            .snapshot();
9513        let reverted_hunk_statuses = snapshot
9514            .git_diff_hunks_in_row_range(0..u32::MAX)
9515            .map(|hunk| hunk.status())
9516            .collect::<Vec<_>>();
9517
9518        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
9519        reverted_hunk_statuses
9520    });
9521    cx.executor().run_until_parked();
9522    cx.assert_editor_state(expected_reverted_text_with_selections);
9523    assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
9524}