tests.rs

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