tests.rs

   1use super::*;
   2use clock::ReplicaId;
   3use collections::BTreeMap;
   4use gpui::{ModelHandle, MutableAppContext};
   5use rand::prelude::*;
   6use std::{
   7    cell::RefCell,
   8    env,
   9    iter::FromIterator,
  10    ops::Range,
  11    rc::Rc,
  12    time::{Duration, Instant},
  13};
  14use unindent::Unindent as _;
  15use util::test::Network;
  16
  17#[cfg(test)]
  18#[ctor::ctor]
  19fn init_logger() {
  20    // std::env::set_var("RUST_LOG", "info");
  21    env_logger::init();
  22}
  23
  24#[test]
  25fn test_select_language() {
  26    let registry = LanguageRegistry {
  27        languages: vec![
  28            Arc::new(Language::new(
  29                LanguageConfig {
  30                    name: "Rust".to_string(),
  31                    path_suffixes: vec!["rs".to_string()],
  32                    ..Default::default()
  33                },
  34                Some(tree_sitter_rust::language()),
  35            )),
  36            Arc::new(Language::new(
  37                LanguageConfig {
  38                    name: "Make".to_string(),
  39                    path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
  40                    ..Default::default()
  41                },
  42                Some(tree_sitter_rust::language()),
  43            )),
  44        ],
  45    };
  46
  47    // matching file extension
  48    assert_eq!(
  49        registry.select_language("zed/lib.rs").map(|l| l.name()),
  50        Some("Rust")
  51    );
  52    assert_eq!(
  53        registry.select_language("zed/lib.mk").map(|l| l.name()),
  54        Some("Make")
  55    );
  56
  57    // matching filename
  58    assert_eq!(
  59        registry.select_language("zed/Makefile").map(|l| l.name()),
  60        Some("Make")
  61    );
  62
  63    // matching suffix that is not the full file extension or filename
  64    assert_eq!(registry.select_language("zed/cars").map(|l| l.name()), None);
  65    assert_eq!(
  66        registry.select_language("zed/a.cars").map(|l| l.name()),
  67        None
  68    );
  69    assert_eq!(registry.select_language("zed/sumk").map(|l| l.name()), None);
  70}
  71
  72#[gpui::test]
  73fn test_edit_events(cx: &mut gpui::MutableAppContext) {
  74    let mut now = Instant::now();
  75    let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
  76    let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
  77
  78    let buffer1 = cx.add_model(|cx| Buffer::new(0, "abcdef", cx));
  79    let buffer2 = cx.add_model(|cx| Buffer::new(1, "abcdef", cx));
  80    let buffer_ops = buffer1.update(cx, |buffer, cx| {
  81        let buffer_1_events = buffer_1_events.clone();
  82        cx.subscribe(&buffer1, move |_, _, event, _| {
  83            buffer_1_events.borrow_mut().push(event.clone())
  84        })
  85        .detach();
  86        let buffer_2_events = buffer_2_events.clone();
  87        cx.subscribe(&buffer2, move |_, _, event, _| {
  88            buffer_2_events.borrow_mut().push(event.clone())
  89        })
  90        .detach();
  91
  92        // An edit emits an edited event, followed by a dirtied event,
  93        // since the buffer was previously in a clean state.
  94        buffer.edit(Some(2..4), "XYZ", cx);
  95
  96        // An empty transaction does not emit any events.
  97        buffer.start_transaction();
  98        buffer.end_transaction(cx);
  99
 100        // A transaction containing two edits emits one edited event.
 101        now += Duration::from_secs(1);
 102        buffer.start_transaction_at(now);
 103        buffer.edit(Some(5..5), "u", cx);
 104        buffer.edit(Some(6..6), "w", cx);
 105        buffer.end_transaction_at(now, cx);
 106
 107        // Undoing a transaction emits one edited event.
 108        buffer.undo(cx);
 109
 110        buffer.operations.clone()
 111    });
 112
 113    // Incorporating a set of remote ops emits a single edited event,
 114    // followed by a dirtied event.
 115    buffer2.update(cx, |buffer, cx| {
 116        buffer.apply_ops(buffer_ops, cx).unwrap();
 117    });
 118
 119    let buffer_1_events = buffer_1_events.borrow();
 120    assert_eq!(
 121        *buffer_1_events,
 122        vec![Event::Edited, Event::Dirtied, Event::Edited, Event::Edited]
 123    );
 124
 125    let buffer_2_events = buffer_2_events.borrow();
 126    assert_eq!(*buffer_2_events, vec![Event::Edited, Event::Dirtied]);
 127}
 128
 129#[gpui::test]
 130async fn test_apply_diff(mut cx: gpui::TestAppContext) {
 131    let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
 132    let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
 133
 134    let text = "a\nccc\ndddd\nffffff\n";
 135    let diff = buffer.read_with(&cx, |b, cx| b.diff(text.into(), cx)).await;
 136    buffer.update(&mut cx, |b, cx| b.apply_diff(diff, cx));
 137    cx.read(|cx| assert_eq!(buffer.read(cx).text(), text));
 138
 139    let text = "a\n1\n\nccc\ndd2dd\nffffff\n";
 140    let diff = buffer.read_with(&cx, |b, cx| b.diff(text.into(), cx)).await;
 141    buffer.update(&mut cx, |b, cx| b.apply_diff(diff, cx));
 142    cx.read(|cx| assert_eq!(buffer.read(cx).text(), text));
 143}
 144
 145#[gpui::test]
 146async fn test_reparse(mut cx: gpui::TestAppContext) {
 147    let text = "fn a() {}";
 148    let buffer = cx.add_model(|cx| {
 149        Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx)
 150    });
 151
 152    // Wait for the initial text to parse
 153    buffer
 154        .condition(&cx, |buffer, _| !buffer.is_parsing())
 155        .await;
 156    assert_eq!(
 157        get_tree_sexp(&buffer, &cx),
 158        concat!(
 159            "(source_file (function_item name: (identifier) ",
 160            "parameters: (parameters) ",
 161            "body: (block)))"
 162        )
 163    );
 164
 165    buffer.update(&mut cx, |buffer, _| {
 166        buffer.set_sync_parse_timeout(Duration::ZERO)
 167    });
 168
 169    // Perform some edits (add parameter and variable reference)
 170    // Parsing doesn't begin until the transaction is complete
 171    buffer.update(&mut cx, |buf, cx| {
 172        buf.start_transaction();
 173
 174        let offset = buf.text().find(")").unwrap();
 175        buf.edit(vec![offset..offset], "b: C", cx);
 176        assert!(!buf.is_parsing());
 177
 178        let offset = buf.text().find("}").unwrap();
 179        buf.edit(vec![offset..offset], " d; ", cx);
 180        assert!(!buf.is_parsing());
 181
 182        buf.end_transaction(cx);
 183        assert_eq!(buf.text(), "fn a(b: C) { d; }");
 184        assert!(buf.is_parsing());
 185    });
 186    buffer
 187        .condition(&cx, |buffer, _| !buffer.is_parsing())
 188        .await;
 189    assert_eq!(
 190        get_tree_sexp(&buffer, &cx),
 191        concat!(
 192            "(source_file (function_item name: (identifier) ",
 193            "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
 194            "body: (block (identifier))))"
 195        )
 196    );
 197
 198    // Perform a series of edits without waiting for the current parse to complete:
 199    // * turn identifier into a field expression
 200    // * turn field expression into a method call
 201    // * add a turbofish to the method call
 202    buffer.update(&mut cx, |buf, cx| {
 203        let offset = buf.text().find(";").unwrap();
 204        buf.edit(vec![offset..offset], ".e", cx);
 205        assert_eq!(buf.text(), "fn a(b: C) { d.e; }");
 206        assert!(buf.is_parsing());
 207    });
 208    buffer.update(&mut cx, |buf, cx| {
 209        let offset = buf.text().find(";").unwrap();
 210        buf.edit(vec![offset..offset], "(f)", cx);
 211        assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }");
 212        assert!(buf.is_parsing());
 213    });
 214    buffer.update(&mut cx, |buf, cx| {
 215        let offset = buf.text().find("(f)").unwrap();
 216        buf.edit(vec![offset..offset], "::<G>", cx);
 217        assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
 218        assert!(buf.is_parsing());
 219    });
 220    buffer
 221        .condition(&cx, |buffer, _| !buffer.is_parsing())
 222        .await;
 223    assert_eq!(
 224        get_tree_sexp(&buffer, &cx),
 225        concat!(
 226            "(source_file (function_item name: (identifier) ",
 227            "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
 228            "body: (block (call_expression ",
 229            "function: (generic_function ",
 230            "function: (field_expression value: (identifier) field: (field_identifier)) ",
 231            "type_arguments: (type_arguments (type_identifier))) ",
 232            "arguments: (arguments (identifier))))))",
 233        )
 234    );
 235
 236    buffer.update(&mut cx, |buf, cx| {
 237        buf.undo(cx);
 238        assert_eq!(buf.text(), "fn a() {}");
 239        assert!(buf.is_parsing());
 240    });
 241    buffer
 242        .condition(&cx, |buffer, _| !buffer.is_parsing())
 243        .await;
 244    assert_eq!(
 245        get_tree_sexp(&buffer, &cx),
 246        concat!(
 247            "(source_file (function_item name: (identifier) ",
 248            "parameters: (parameters) ",
 249            "body: (block)))"
 250        )
 251    );
 252
 253    buffer.update(&mut cx, |buf, cx| {
 254        buf.redo(cx);
 255        assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
 256        assert!(buf.is_parsing());
 257    });
 258    buffer
 259        .condition(&cx, |buffer, _| !buffer.is_parsing())
 260        .await;
 261    assert_eq!(
 262        get_tree_sexp(&buffer, &cx),
 263        concat!(
 264            "(source_file (function_item name: (identifier) ",
 265            "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
 266            "body: (block (call_expression ",
 267            "function: (generic_function ",
 268            "function: (field_expression value: (identifier) field: (field_identifier)) ",
 269            "type_arguments: (type_arguments (type_identifier))) ",
 270            "arguments: (arguments (identifier))))))",
 271        )
 272    );
 273
 274    fn get_tree_sexp(buffer: &ModelHandle<Buffer>, cx: &gpui::TestAppContext) -> String {
 275        buffer.read_with(cx, |buffer, _| {
 276            buffer.syntax_tree().unwrap().root_node().to_sexp()
 277        })
 278    }
 279}
 280
 281#[gpui::test]
 282fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
 283    let buffer = cx.add_model(|cx| {
 284        let text = "
 285            mod x {
 286                mod y {
 287
 288                }
 289            }
 290        "
 291        .unindent();
 292        Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx)
 293    });
 294    let buffer = buffer.read(cx);
 295    assert_eq!(
 296        buffer.enclosing_bracket_point_ranges(Point::new(1, 6)..Point::new(1, 6)),
 297        Some((
 298            Point::new(0, 6)..Point::new(0, 7),
 299            Point::new(4, 0)..Point::new(4, 1)
 300        ))
 301    );
 302    assert_eq!(
 303        buffer.enclosing_bracket_point_ranges(Point::new(1, 10)..Point::new(1, 10)),
 304        Some((
 305            Point::new(1, 10)..Point::new(1, 11),
 306            Point::new(3, 4)..Point::new(3, 5)
 307        ))
 308    );
 309    assert_eq!(
 310        buffer.enclosing_bracket_point_ranges(Point::new(3, 5)..Point::new(3, 5)),
 311        Some((
 312            Point::new(1, 10)..Point::new(1, 11),
 313            Point::new(3, 4)..Point::new(3, 5)
 314        ))
 315    );
 316}
 317
 318#[gpui::test]
 319fn test_edit_with_autoindent(cx: &mut MutableAppContext) {
 320    cx.add_model(|cx| {
 321        let text = "fn a() {}";
 322        let mut buffer =
 323            Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx);
 324
 325        buffer.edit_with_autoindent([8..8], "\n\n", cx);
 326        assert_eq!(buffer.text(), "fn a() {\n    \n}");
 327
 328        buffer.edit_with_autoindent([Point::new(1, 4)..Point::new(1, 4)], "b()\n", cx);
 329        assert_eq!(buffer.text(), "fn a() {\n    b()\n    \n}");
 330
 331        buffer.edit_with_autoindent([Point::new(2, 4)..Point::new(2, 4)], ".c", cx);
 332        assert_eq!(buffer.text(), "fn a() {\n    b()\n        .c\n}");
 333
 334        buffer
 335    });
 336}
 337
 338#[gpui::test]
 339fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut MutableAppContext) {
 340    cx.add_model(|cx| {
 341        let text = "
 342            fn a() {
 343            c;
 344            d;
 345            }
 346        "
 347        .unindent();
 348
 349        let mut buffer =
 350            Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx);
 351
 352        // Lines 2 and 3 don't match the indentation suggestion. When editing these lines,
 353        // their indentation is not adjusted.
 354        buffer.edit_with_autoindent([empty(Point::new(1, 1)), empty(Point::new(2, 1))], "()", cx);
 355        assert_eq!(
 356            buffer.text(),
 357            "
 358            fn a() {
 359            c();
 360            d();
 361            }
 362            "
 363            .unindent()
 364        );
 365
 366        // When appending new content after these lines, the indentation is based on the
 367        // preceding lines' actual indentation.
 368        buffer.edit_with_autoindent(
 369            [empty(Point::new(1, 1)), empty(Point::new(2, 1))],
 370            "\n.f\n.g",
 371            cx,
 372        );
 373        assert_eq!(
 374            buffer.text(),
 375            "
 376            fn a() {
 377            c
 378                .f
 379                .g();
 380            d
 381                .f
 382                .g();
 383            }
 384            "
 385            .unindent()
 386        );
 387        buffer
 388    });
 389}
 390
 391#[gpui::test]
 392fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppContext) {
 393    cx.add_model(|cx| {
 394        let text = "
 395            fn a() {}
 396        "
 397        .unindent();
 398
 399        let mut buffer =
 400            Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx);
 401
 402        buffer.edit_with_autoindent([5..5], "\nb", cx);
 403        assert_eq!(
 404            buffer.text(),
 405            "
 406                fn a(
 407                    b) {}
 408            "
 409            .unindent()
 410        );
 411
 412        // The indentation suggestion changed because `@end` node (a close paren)
 413        // is now at the beginning of the line.
 414        buffer.edit_with_autoindent([Point::new(1, 4)..Point::new(1, 5)], "", cx);
 415        assert_eq!(
 416            buffer.text(),
 417            "
 418                fn a(
 419                ) {}
 420            "
 421            .unindent()
 422        );
 423
 424        buffer
 425    });
 426}
 427
 428#[gpui::test]
 429async fn test_diagnostics(mut cx: gpui::TestAppContext) {
 430    let (language_server, mut fake) = lsp::LanguageServer::fake(cx.background()).await;
 431    let mut rust_lang = rust_lang();
 432    rust_lang.config.language_server = Some(LanguageServerConfig {
 433        disk_based_diagnostic_sources: HashSet::from_iter(["disk".to_string()]),
 434        ..Default::default()
 435    });
 436
 437    let text = "
 438        fn a() { A }
 439        fn b() { BB }
 440        fn c() { CCC }
 441    "
 442    .unindent();
 443
 444    let buffer = cx.add_model(|cx| {
 445        Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang)), Some(language_server), cx)
 446    });
 447
 448    let open_notification = fake
 449        .receive_notification::<lsp::notification::DidOpenTextDocument>()
 450        .await;
 451
 452    // Edit the buffer, moving the content down
 453    buffer.update(&mut cx, |buffer, cx| buffer.edit([0..0], "\n\n", cx));
 454    let change_notification_1 = fake
 455        .receive_notification::<lsp::notification::DidChangeTextDocument>()
 456        .await;
 457    assert!(change_notification_1.text_document.version > open_notification.text_document.version);
 458
 459    buffer.update(&mut cx, |buffer, cx| {
 460        // Receive diagnostics for an earlier version of the buffer.
 461        buffer
 462            .update_diagnostics(
 463                Some(open_notification.text_document.version),
 464                vec![
 465                    DiagnosticEntry {
 466                        range: PointUtf16::new(0, 9)..PointUtf16::new(0, 10),
 467                        diagnostic: Diagnostic {
 468                            severity: DiagnosticSeverity::ERROR,
 469                            message: "undefined variable 'A'".to_string(),
 470                            is_disk_based: true,
 471                            group_id: 0,
 472                            is_primary: true,
 473                            ..Default::default()
 474                        },
 475                    },
 476                    DiagnosticEntry {
 477                        range: PointUtf16::new(1, 9)..PointUtf16::new(1, 11),
 478                        diagnostic: Diagnostic {
 479                            severity: DiagnosticSeverity::ERROR,
 480                            message: "undefined variable 'BB'".to_string(),
 481                            is_disk_based: true,
 482                            group_id: 1,
 483                            is_primary: true,
 484                            ..Default::default()
 485                        },
 486                    },
 487                    DiagnosticEntry {
 488                        range: PointUtf16::new(2, 9)..PointUtf16::new(2, 12),
 489                        diagnostic: Diagnostic {
 490                            severity: DiagnosticSeverity::ERROR,
 491                            is_disk_based: true,
 492                            message: "undefined variable 'CCC'".to_string(),
 493                            group_id: 2,
 494                            is_primary: true,
 495                            ..Default::default()
 496                        },
 497                    },
 498                ],
 499                cx,
 500            )
 501            .unwrap();
 502
 503        // The diagnostics have moved down since they were created.
 504        assert_eq!(
 505            buffer
 506                .snapshot()
 507                .diagnostics_in_range::<_, Point>(Point::new(3, 0)..Point::new(5, 0))
 508                .collect::<Vec<_>>(),
 509            &[
 510                DiagnosticEntry {
 511                    range: Point::new(3, 9)..Point::new(3, 11),
 512                    diagnostic: Diagnostic {
 513                        severity: DiagnosticSeverity::ERROR,
 514                        message: "undefined variable 'BB'".to_string(),
 515                        is_disk_based: true,
 516                        group_id: 1,
 517                        is_primary: true,
 518                        ..Default::default()
 519                    },
 520                },
 521                DiagnosticEntry {
 522                    range: Point::new(4, 9)..Point::new(4, 12),
 523                    diagnostic: Diagnostic {
 524                        severity: DiagnosticSeverity::ERROR,
 525                        message: "undefined variable 'CCC'".to_string(),
 526                        is_disk_based: true,
 527                        group_id: 2,
 528                        is_primary: true,
 529                        ..Default::default()
 530                    }
 531                }
 532            ]
 533        );
 534        assert_eq!(
 535            chunks_with_diagnostics(buffer, 0..buffer.len()),
 536            [
 537                ("\n\nfn a() { ".to_string(), None),
 538                ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
 539                (" }\nfn b() { ".to_string(), None),
 540                ("BB".to_string(), Some(DiagnosticSeverity::ERROR)),
 541                (" }\nfn c() { ".to_string(), None),
 542                ("CCC".to_string(), Some(DiagnosticSeverity::ERROR)),
 543                (" }\n".to_string(), None),
 544            ]
 545        );
 546        assert_eq!(
 547            chunks_with_diagnostics(buffer, Point::new(3, 10)..Point::new(4, 11)),
 548            [
 549                ("B".to_string(), Some(DiagnosticSeverity::ERROR)),
 550                (" }\nfn c() { ".to_string(), None),
 551                ("CC".to_string(), Some(DiagnosticSeverity::ERROR)),
 552            ]
 553        );
 554
 555        // Ensure overlapping diagnostics are highlighted correctly.
 556        buffer
 557            .update_diagnostics(
 558                Some(open_notification.text_document.version),
 559                vec![
 560                    DiagnosticEntry {
 561                        range: PointUtf16::new(0, 9)..PointUtf16::new(0, 10),
 562                        diagnostic: Diagnostic {
 563                            severity: DiagnosticSeverity::ERROR,
 564                            message: "undefined variable 'A'".to_string(),
 565                            is_disk_based: true,
 566                            group_id: 0,
 567                            is_primary: true,
 568                            ..Default::default()
 569                        },
 570                    },
 571                    DiagnosticEntry {
 572                        range: PointUtf16::new(0, 9)..PointUtf16::new(0, 12),
 573                        diagnostic: Diagnostic {
 574                            severity: DiagnosticSeverity::WARNING,
 575                            message: "unreachable statement".to_string(),
 576                            group_id: 1,
 577                            is_primary: true,
 578                            ..Default::default()
 579                        },
 580                    },
 581                ],
 582                cx,
 583            )
 584            .unwrap();
 585        assert_eq!(
 586            buffer
 587                .snapshot()
 588                .diagnostics_in_range::<_, Point>(Point::new(2, 0)..Point::new(3, 0))
 589                .collect::<Vec<_>>(),
 590            &[
 591                DiagnosticEntry {
 592                    range: Point::new(2, 9)..Point::new(2, 12),
 593                    diagnostic: Diagnostic {
 594                        severity: DiagnosticSeverity::WARNING,
 595                        message: "unreachable statement".to_string(),
 596                        group_id: 1,
 597                        is_primary: true,
 598                        ..Default::default()
 599                    }
 600                },
 601                DiagnosticEntry {
 602                    range: Point::new(2, 9)..Point::new(2, 10),
 603                    diagnostic: Diagnostic {
 604                        severity: DiagnosticSeverity::ERROR,
 605                        message: "undefined variable 'A'".to_string(),
 606                        is_disk_based: true,
 607                        group_id: 0,
 608                        is_primary: true,
 609                        ..Default::default()
 610                    },
 611                }
 612            ]
 613        );
 614        assert_eq!(
 615            chunks_with_diagnostics(buffer, Point::new(2, 0)..Point::new(3, 0)),
 616            [
 617                ("fn a() { ".to_string(), None),
 618                ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
 619                (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
 620                ("\n".to_string(), None),
 621            ]
 622        );
 623        assert_eq!(
 624            chunks_with_diagnostics(buffer, Point::new(2, 10)..Point::new(3, 0)),
 625            [
 626                (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
 627                ("\n".to_string(), None),
 628            ]
 629        );
 630    });
 631
 632    // Keep editing the buffer and ensure disk-based diagnostics get translated according to the
 633    // changes since the last save.
 634    buffer.update(&mut cx, |buffer, cx| {
 635        buffer.edit(Some(Point::new(2, 0)..Point::new(2, 0)), "    ", cx);
 636        buffer.edit(Some(Point::new(2, 8)..Point::new(2, 10)), "(x: usize)", cx);
 637    });
 638    let change_notification_2 = fake
 639        .receive_notification::<lsp::notification::DidChangeTextDocument>()
 640        .await;
 641    assert!(
 642        change_notification_2.text_document.version > change_notification_1.text_document.version
 643    );
 644
 645    buffer.update(&mut cx, |buffer, cx| {
 646        buffer
 647            .update_diagnostics(
 648                Some(change_notification_2.text_document.version),
 649                vec![
 650                    DiagnosticEntry {
 651                        range: PointUtf16::new(1, 9)..PointUtf16::new(1, 11),
 652                        diagnostic: Diagnostic {
 653                            severity: DiagnosticSeverity::ERROR,
 654                            message: "undefined variable 'BB'".to_string(),
 655                            is_disk_based: true,
 656                            group_id: 1,
 657                            is_primary: true,
 658                            ..Default::default()
 659                        },
 660                    },
 661                    DiagnosticEntry {
 662                        range: PointUtf16::new(0, 9)..PointUtf16::new(0, 10),
 663                        diagnostic: Diagnostic {
 664                            severity: DiagnosticSeverity::ERROR,
 665                            message: "undefined variable 'A'".to_string(),
 666                            is_disk_based: true,
 667                            group_id: 0,
 668                            is_primary: true,
 669                            ..Default::default()
 670                        },
 671                    },
 672                ],
 673                cx,
 674            )
 675            .unwrap();
 676        assert_eq!(
 677            buffer
 678                .snapshot()
 679                .diagnostics_in_range::<_, Point>(0..buffer.len())
 680                .collect::<Vec<_>>(),
 681            &[
 682                DiagnosticEntry {
 683                    range: Point::new(2, 21)..Point::new(2, 22),
 684                    diagnostic: Diagnostic {
 685                        severity: DiagnosticSeverity::ERROR,
 686                        message: "undefined variable 'A'".to_string(),
 687                        is_disk_based: true,
 688                        group_id: 0,
 689                        is_primary: true,
 690                        ..Default::default()
 691                    }
 692                },
 693                DiagnosticEntry {
 694                    range: Point::new(3, 9)..Point::new(3, 11),
 695                    diagnostic: Diagnostic {
 696                        severity: DiagnosticSeverity::ERROR,
 697                        message: "undefined variable 'BB'".to_string(),
 698                        is_disk_based: true,
 699                        group_id: 1,
 700                        is_primary: true,
 701                        ..Default::default()
 702                    },
 703                }
 704            ]
 705        );
 706    });
 707}
 708
 709#[gpui::test]
 710async fn test_empty_diagnostic_ranges(mut cx: gpui::TestAppContext) {
 711    cx.add_model(|cx| {
 712        let text = concat!(
 713            "let one = ;\n", //
 714            "let two = \n",
 715            "let three = 3;\n",
 716        );
 717
 718        let mut buffer = Buffer::new(0, text, cx);
 719        buffer.set_language(Some(Arc::new(rust_lang())), None, cx);
 720        buffer
 721            .update_diagnostics(
 722                None,
 723                vec![
 724                    DiagnosticEntry {
 725                        range: PointUtf16::new(0, 10)..PointUtf16::new(0, 10),
 726                        diagnostic: Diagnostic {
 727                            severity: DiagnosticSeverity::ERROR,
 728                            message: "syntax error 1".to_string(),
 729                            ..Default::default()
 730                        },
 731                    },
 732                    DiagnosticEntry {
 733                        range: PointUtf16::new(1, 10)..PointUtf16::new(1, 10),
 734                        diagnostic: Diagnostic {
 735                            severity: DiagnosticSeverity::ERROR,
 736                            message: "syntax error 2".to_string(),
 737                            ..Default::default()
 738                        },
 739                    },
 740                ],
 741                cx,
 742            )
 743            .unwrap();
 744
 745        // An empty range is extended forward to include the following character.
 746        // At the end of a line, an empty range is extended backward to include
 747        // the preceding character.
 748        let chunks = chunks_with_diagnostics(&buffer, 0..buffer.len());
 749        assert_eq!(
 750            chunks
 751                .iter()
 752                .map(|(s, d)| (s.as_str(), *d))
 753                .collect::<Vec<_>>(),
 754            &[
 755                ("let one = ", None),
 756                (";", Some(DiagnosticSeverity::ERROR)),
 757                ("\nlet two =", None),
 758                (" ", Some(DiagnosticSeverity::ERROR)),
 759                ("\nlet three = 3;\n", None)
 760            ]
 761        );
 762        buffer
 763    });
 764}
 765
 766#[gpui::test]
 767fn test_serialization(cx: &mut gpui::MutableAppContext) {
 768    let mut now = Instant::now();
 769
 770    let buffer1 = cx.add_model(|cx| {
 771        let mut buffer = Buffer::new(0, "abc", cx);
 772        buffer.edit([3..3], "D", cx);
 773
 774        now += Duration::from_secs(1);
 775        buffer.start_transaction_at(now);
 776        buffer.edit([4..4], "E", cx);
 777        buffer.end_transaction_at(now, cx);
 778        assert_eq!(buffer.text(), "abcDE");
 779
 780        buffer.undo(cx);
 781        assert_eq!(buffer.text(), "abcD");
 782
 783        buffer.edit([4..4], "F", cx);
 784        assert_eq!(buffer.text(), "abcDF");
 785        buffer
 786    });
 787    assert_eq!(buffer1.read(cx).text(), "abcDF");
 788
 789    let message = buffer1.read(cx).to_proto();
 790    let buffer2 = cx.add_model(|cx| Buffer::from_proto(1, message, None, cx).unwrap());
 791    assert_eq!(buffer2.read(cx).text(), "abcDF");
 792}
 793
 794#[gpui::test(iterations = 100)]
 795fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) {
 796    let min_peers = env::var("MIN_PEERS")
 797        .map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
 798        .unwrap_or(1);
 799    let max_peers = env::var("MAX_PEERS")
 800        .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
 801        .unwrap_or(5);
 802    let operations = env::var("OPERATIONS")
 803        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
 804        .unwrap_or(10);
 805
 806    let base_text_len = rng.gen_range(0..10);
 807    let base_text = RandomCharIter::new(&mut rng)
 808        .take(base_text_len)
 809        .collect::<String>();
 810    let mut replica_ids = Vec::new();
 811    let mut buffers = Vec::new();
 812    let mut network = Network::new(rng.clone());
 813
 814    for i in 0..rng.gen_range(min_peers..=max_peers) {
 815        let buffer = cx.add_model(|cx| {
 816            let mut buffer = Buffer::new(i as ReplicaId, base_text.as_str(), cx);
 817            buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
 818            buffer
 819        });
 820        buffers.push(buffer);
 821        replica_ids.push(i as ReplicaId);
 822        network.add_peer(i as ReplicaId);
 823        log::info!("Adding initial peer with replica id {}", i);
 824    }
 825
 826    log::info!("initial text: {:?}", base_text);
 827
 828    let mut now = Instant::now();
 829    let mut mutation_count = operations;
 830    let mut active_selections = BTreeMap::default();
 831    loop {
 832        let replica_index = rng.gen_range(0..replica_ids.len());
 833        let replica_id = replica_ids[replica_index];
 834        let buffer = &mut buffers[replica_index];
 835        let mut new_buffer = None;
 836        match rng.gen_range(0..100) {
 837            0..=29 if mutation_count != 0 => {
 838                buffer.update(cx, |buffer, cx| {
 839                    buffer.start_transaction_at(now);
 840                    buffer.randomly_edit(&mut rng, 5, cx);
 841                    buffer.end_transaction_at(now, cx);
 842                    log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
 843                });
 844                mutation_count -= 1;
 845            }
 846            30..=39 if mutation_count != 0 => {
 847                buffer.update(cx, |buffer, cx| {
 848                    let mut selections = Vec::new();
 849                    for id in 0..rng.gen_range(1..=5) {
 850                        let range = buffer.random_byte_range(0, &mut rng);
 851                        selections.push(Selection {
 852                            id,
 853                            start: buffer.anchor_before(range.start),
 854                            end: buffer.anchor_before(range.end),
 855                            reversed: false,
 856                            goal: SelectionGoal::None,
 857                        });
 858                    }
 859                    let selections: Arc<[Selection<Anchor>]> = selections.into();
 860                    log::info!(
 861                        "peer {} setting active selections: {:?}",
 862                        replica_id,
 863                        selections
 864                    );
 865                    active_selections.insert(replica_id, selections.clone());
 866                    buffer.set_active_selections(selections, cx);
 867                });
 868                mutation_count -= 1;
 869            }
 870            40..=49 if replica_ids.len() < max_peers => {
 871                let old_buffer = buffer.read(cx).to_proto();
 872                let new_replica_id = replica_ids.len() as ReplicaId;
 873                log::info!(
 874                    "Adding new replica {} (replicating from {})",
 875                    new_replica_id,
 876                    replica_id
 877                );
 878                new_buffer = Some(cx.add_model(|cx| {
 879                    let mut new_buffer =
 880                        Buffer::from_proto(new_replica_id, old_buffer, None, cx).unwrap();
 881                    new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
 882                    new_buffer
 883                }));
 884                replica_ids.push(new_replica_id);
 885                network.replicate(replica_id, new_replica_id);
 886            }
 887            50..=69 if mutation_count != 0 => {
 888                buffer.update(cx, |buffer, cx| {
 889                    buffer.randomly_undo_redo(&mut rng, cx);
 890                    log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
 891                });
 892                mutation_count -= 1;
 893            }
 894            70..=99 if network.has_unreceived(replica_id) => {
 895                let ops = network
 896                    .receive(replica_id)
 897                    .into_iter()
 898                    .map(|op| proto::deserialize_operation(op).unwrap());
 899                if ops.len() > 0 {
 900                    log::info!(
 901                        "peer {} applying {} ops from the network.",
 902                        replica_id,
 903                        ops.len()
 904                    );
 905                    buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx).unwrap());
 906                }
 907            }
 908            _ => {}
 909        }
 910
 911        buffer.update(cx, |buffer, _| {
 912            let ops = buffer
 913                .operations
 914                .drain(..)
 915                .map(|op| proto::serialize_operation(&op))
 916                .collect();
 917            network.broadcast(buffer.replica_id(), ops);
 918        });
 919        now += Duration::from_millis(rng.gen_range(0..=200));
 920        buffers.extend(new_buffer);
 921
 922        for buffer in &buffers {
 923            buffer.read(cx).check_invariants();
 924        }
 925
 926        if mutation_count == 0 && network.is_idle() {
 927            break;
 928        }
 929    }
 930
 931    let first_buffer = buffers[0].read(cx);
 932    for buffer in &buffers[1..] {
 933        let buffer = buffer.read(cx);
 934        assert_eq!(
 935            buffer.text(),
 936            first_buffer.text(),
 937            "Replica {} text != Replica 0 text",
 938            buffer.replica_id()
 939        );
 940    }
 941
 942    for buffer in &buffers {
 943        let buffer = buffer.read(cx).snapshot();
 944        let expected_remote_selections = active_selections
 945            .iter()
 946            .filter(|(replica_id, _)| **replica_id != buffer.replica_id())
 947            .map(|(replica_id, selections)| (*replica_id, selections.iter().collect::<Vec<_>>()))
 948            .collect::<Vec<_>>();
 949        let actual_remote_selections = buffer
 950            .remote_selections_in_range(Anchor::min()..Anchor::max())
 951            .map(|(replica_id, selections)| (replica_id, selections.collect::<Vec<_>>()))
 952            .collect::<Vec<_>>();
 953        assert_eq!(actual_remote_selections, expected_remote_selections);
 954    }
 955}
 956
 957fn chunks_with_diagnostics<T: ToOffset + ToPoint>(
 958    buffer: &Buffer,
 959    range: Range<T>,
 960) -> Vec<(String, Option<DiagnosticSeverity>)> {
 961    let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = Vec::new();
 962    for chunk in buffer.snapshot().chunks(range, Some(&Default::default())) {
 963        if chunks
 964            .last()
 965            .map_or(false, |prev_chunk| prev_chunk.1 == chunk.diagnostic)
 966        {
 967            chunks.last_mut().unwrap().0.push_str(chunk.text);
 968        } else {
 969            chunks.push((chunk.text.to_string(), chunk.diagnostic));
 970        }
 971    }
 972    chunks
 973}
 974
 975#[test]
 976fn test_contiguous_ranges() {
 977    assert_eq!(
 978        contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::<Vec<_>>(),
 979        &[1..4, 5..7, 9..13]
 980    );
 981
 982    // Respects the `max_len` parameter
 983    assert_eq!(
 984        contiguous_ranges(
 985            [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(),
 986            3
 987        )
 988        .collect::<Vec<_>>(),
 989        &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
 990    );
 991}
 992
 993impl Buffer {
 994    pub fn enclosing_bracket_point_ranges<T: ToOffset>(
 995        &self,
 996        range: Range<T>,
 997    ) -> Option<(Range<Point>, Range<Point>)> {
 998        self.snapshot()
 999            .enclosing_bracket_ranges(range)
1000            .map(|(start, end)| {
1001                let point_start = start.start.to_point(self)..start.end.to_point(self);
1002                let point_end = end.start.to_point(self)..end.end.to_point(self);
1003                (point_start, point_end)
1004            })
1005    }
1006}
1007
1008fn rust_lang() -> Language {
1009    Language::new(
1010        LanguageConfig {
1011            name: "Rust".to_string(),
1012            path_suffixes: vec!["rs".to_string()],
1013            language_server: None,
1014            ..Default::default()
1015        },
1016        Some(tree_sitter_rust::language()),
1017    )
1018    .with_indents_query(
1019        r#"
1020                (call_expression) @indent
1021                (field_expression) @indent
1022                (_ "(" ")" @end) @indent
1023                (_ "{" "}" @end) @indent
1024            "#,
1025    )
1026    .unwrap()
1027    .with_brackets_query(r#" ("{" @open "}" @close) "#)
1028    .unwrap()
1029}
1030
1031fn empty(point: Point) -> Range<Point> {
1032    point..point
1033}