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                "lsp".into(),
 464                Some(open_notification.text_document.version),
 465                vec![
 466                    DiagnosticEntry {
 467                        range: PointUtf16::new(0, 9)..PointUtf16::new(0, 10),
 468                        diagnostic: Diagnostic {
 469                            severity: DiagnosticSeverity::ERROR,
 470                            message: "undefined variable 'A'".to_string(),
 471                            is_disk_based: true,
 472                            group_id: 0,
 473                            is_primary: true,
 474                            ..Default::default()
 475                        },
 476                    },
 477                    DiagnosticEntry {
 478                        range: PointUtf16::new(1, 9)..PointUtf16::new(1, 11),
 479                        diagnostic: Diagnostic {
 480                            severity: DiagnosticSeverity::ERROR,
 481                            message: "undefined variable 'BB'".to_string(),
 482                            is_disk_based: true,
 483                            group_id: 1,
 484                            is_primary: true,
 485                            ..Default::default()
 486                        },
 487                    },
 488                    DiagnosticEntry {
 489                        range: PointUtf16::new(2, 9)..PointUtf16::new(2, 12),
 490                        diagnostic: Diagnostic {
 491                            severity: DiagnosticSeverity::ERROR,
 492                            is_disk_based: true,
 493                            message: "undefined variable 'CCC'".to_string(),
 494                            group_id: 2,
 495                            is_primary: true,
 496                            ..Default::default()
 497                        },
 498                    },
 499                ],
 500                cx,
 501            )
 502            .unwrap();
 503
 504        // The diagnostics have moved down since they were created.
 505        assert_eq!(
 506            buffer
 507                .snapshot()
 508                .diagnostics_in_range::<_, Point>(Point::new(3, 0)..Point::new(5, 0))
 509                .collect::<Vec<_>>(),
 510            &[
 511                (
 512                    "lsp",
 513                    DiagnosticEntry {
 514                        range: Point::new(3, 9)..Point::new(3, 11),
 515                        diagnostic: Diagnostic {
 516                            severity: DiagnosticSeverity::ERROR,
 517                            message: "undefined variable 'BB'".to_string(),
 518                            is_disk_based: true,
 519                            group_id: 1,
 520                            is_primary: true,
 521                            ..Default::default()
 522                        },
 523                    }
 524                ),
 525                (
 526                    "lsp",
 527                    DiagnosticEntry {
 528                        range: Point::new(4, 9)..Point::new(4, 12),
 529                        diagnostic: Diagnostic {
 530                            severity: DiagnosticSeverity::ERROR,
 531                            message: "undefined variable 'CCC'".to_string(),
 532                            is_disk_based: true,
 533                            group_id: 2,
 534                            is_primary: true,
 535                            ..Default::default()
 536                        }
 537                    }
 538                )
 539            ]
 540        );
 541        assert_eq!(
 542            chunks_with_diagnostics(buffer, 0..buffer.len()),
 543            [
 544                ("\n\nfn a() { ".to_string(), None),
 545                ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
 546                (" }\nfn b() { ".to_string(), None),
 547                ("BB".to_string(), Some(DiagnosticSeverity::ERROR)),
 548                (" }\nfn c() { ".to_string(), None),
 549                ("CCC".to_string(), Some(DiagnosticSeverity::ERROR)),
 550                (" }\n".to_string(), None),
 551            ]
 552        );
 553        assert_eq!(
 554            chunks_with_diagnostics(buffer, Point::new(3, 10)..Point::new(4, 11)),
 555            [
 556                ("B".to_string(), Some(DiagnosticSeverity::ERROR)),
 557                (" }\nfn c() { ".to_string(), None),
 558                ("CC".to_string(), Some(DiagnosticSeverity::ERROR)),
 559            ]
 560        );
 561
 562        // Ensure overlapping diagnostics are highlighted correctly.
 563        buffer
 564            .update_diagnostics(
 565                "lsp".into(),
 566                Some(open_notification.text_document.version),
 567                vec![
 568                    DiagnosticEntry {
 569                        range: PointUtf16::new(0, 9)..PointUtf16::new(0, 10),
 570                        diagnostic: Diagnostic {
 571                            severity: DiagnosticSeverity::ERROR,
 572                            message: "undefined variable 'A'".to_string(),
 573                            is_disk_based: true,
 574                            group_id: 0,
 575                            is_primary: true,
 576                            ..Default::default()
 577                        },
 578                    },
 579                    DiagnosticEntry {
 580                        range: PointUtf16::new(0, 9)..PointUtf16::new(0, 12),
 581                        diagnostic: Diagnostic {
 582                            severity: DiagnosticSeverity::WARNING,
 583                            message: "unreachable statement".to_string(),
 584                            group_id: 1,
 585                            is_primary: true,
 586                            ..Default::default()
 587                        },
 588                    },
 589                ],
 590                cx,
 591            )
 592            .unwrap();
 593        assert_eq!(
 594            buffer
 595                .snapshot()
 596                .diagnostics_in_range::<_, Point>(Point::new(2, 0)..Point::new(3, 0))
 597                .collect::<Vec<_>>(),
 598            &[
 599                (
 600                    "lsp",
 601                    DiagnosticEntry {
 602                        range: Point::new(2, 9)..Point::new(2, 12),
 603                        diagnostic: Diagnostic {
 604                            severity: DiagnosticSeverity::WARNING,
 605                            message: "unreachable statement".to_string(),
 606                            group_id: 1,
 607                            is_primary: true,
 608                            ..Default::default()
 609                        }
 610                    }
 611                ),
 612                (
 613                    "lsp",
 614                    DiagnosticEntry {
 615                        range: Point::new(2, 9)..Point::new(2, 10),
 616                        diagnostic: Diagnostic {
 617                            severity: DiagnosticSeverity::ERROR,
 618                            message: "undefined variable 'A'".to_string(),
 619                            is_disk_based: true,
 620                            group_id: 0,
 621                            is_primary: true,
 622                            ..Default::default()
 623                        },
 624                    }
 625                )
 626            ]
 627        );
 628        assert_eq!(
 629            chunks_with_diagnostics(buffer, Point::new(2, 0)..Point::new(3, 0)),
 630            [
 631                ("fn a() { ".to_string(), None),
 632                ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
 633                (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
 634                ("\n".to_string(), None),
 635            ]
 636        );
 637        assert_eq!(
 638            chunks_with_diagnostics(buffer, Point::new(2, 10)..Point::new(3, 0)),
 639            [
 640                (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
 641                ("\n".to_string(), None),
 642            ]
 643        );
 644    });
 645
 646    // Keep editing the buffer and ensure disk-based diagnostics get translated according to the
 647    // changes since the last save.
 648    buffer.update(&mut cx, |buffer, cx| {
 649        buffer.edit(Some(Point::new(2, 0)..Point::new(2, 0)), "    ", cx);
 650        buffer.edit(Some(Point::new(2, 8)..Point::new(2, 10)), "(x: usize)", cx);
 651    });
 652    let change_notification_2 = fake
 653        .receive_notification::<lsp::notification::DidChangeTextDocument>()
 654        .await;
 655    assert!(
 656        change_notification_2.text_document.version > change_notification_1.text_document.version
 657    );
 658
 659    buffer.update(&mut cx, |buffer, cx| {
 660        buffer
 661            .update_diagnostics(
 662                "lsp".into(),
 663                Some(change_notification_2.text_document.version),
 664                vec![
 665                    DiagnosticEntry {
 666                        range: PointUtf16::new(1, 9)..PointUtf16::new(1, 11),
 667                        diagnostic: Diagnostic {
 668                            severity: DiagnosticSeverity::ERROR,
 669                            message: "undefined variable 'BB'".to_string(),
 670                            is_disk_based: true,
 671                            group_id: 1,
 672                            is_primary: true,
 673                            ..Default::default()
 674                        },
 675                    },
 676                    DiagnosticEntry {
 677                        range: PointUtf16::new(0, 9)..PointUtf16::new(0, 10),
 678                        diagnostic: Diagnostic {
 679                            severity: DiagnosticSeverity::ERROR,
 680                            message: "undefined variable 'A'".to_string(),
 681                            is_disk_based: true,
 682                            group_id: 0,
 683                            is_primary: true,
 684                            ..Default::default()
 685                        },
 686                    },
 687                ],
 688                cx,
 689            )
 690            .unwrap();
 691        assert_eq!(
 692            buffer
 693                .snapshot()
 694                .diagnostics_in_range::<_, Point>(0..buffer.len())
 695                .collect::<Vec<_>>(),
 696            &[
 697                (
 698                    "lsp",
 699                    DiagnosticEntry {
 700                        range: Point::new(2, 21)..Point::new(2, 22),
 701                        diagnostic: Diagnostic {
 702                            severity: DiagnosticSeverity::ERROR,
 703                            message: "undefined variable 'A'".to_string(),
 704                            is_disk_based: true,
 705                            group_id: 0,
 706                            is_primary: true,
 707                            ..Default::default()
 708                        }
 709                    }
 710                ),
 711                (
 712                    "lsp",
 713                    DiagnosticEntry {
 714                        range: Point::new(3, 9)..Point::new(3, 11),
 715                        diagnostic: Diagnostic {
 716                            severity: DiagnosticSeverity::ERROR,
 717                            message: "undefined variable 'BB'".to_string(),
 718                            is_disk_based: true,
 719                            group_id: 1,
 720                            is_primary: true,
 721                            ..Default::default()
 722                        },
 723                    }
 724                )
 725            ]
 726        );
 727    });
 728}
 729
 730#[gpui::test]
 731async fn test_empty_diagnostic_ranges(mut cx: gpui::TestAppContext) {
 732    cx.add_model(|cx| {
 733        let text = concat!(
 734            "let one = ;\n", //
 735            "let two = \n",
 736            "let three = 3;\n",
 737        );
 738
 739        let mut buffer = Buffer::new(0, text, cx);
 740        buffer.set_language(Some(Arc::new(rust_lang())), None, cx);
 741        buffer
 742            .update_diagnostics(
 743                "lsp".into(),
 744                None,
 745                vec![
 746                    DiagnosticEntry {
 747                        range: PointUtf16::new(0, 10)..PointUtf16::new(0, 10),
 748                        diagnostic: Diagnostic {
 749                            severity: DiagnosticSeverity::ERROR,
 750                            message: "syntax error 1".to_string(),
 751                            ..Default::default()
 752                        },
 753                    },
 754                    DiagnosticEntry {
 755                        range: PointUtf16::new(1, 10)..PointUtf16::new(1, 10),
 756                        diagnostic: Diagnostic {
 757                            severity: DiagnosticSeverity::ERROR,
 758                            message: "syntax error 2".to_string(),
 759                            ..Default::default()
 760                        },
 761                    },
 762                ],
 763                cx,
 764            )
 765            .unwrap();
 766
 767        // An empty range is extended forward to include the following character.
 768        // At the end of a line, an empty range is extended backward to include
 769        // the preceding character.
 770        let chunks = chunks_with_diagnostics(&buffer, 0..buffer.len());
 771        assert_eq!(
 772            chunks
 773                .iter()
 774                .map(|(s, d)| (s.as_str(), *d))
 775                .collect::<Vec<_>>(),
 776            &[
 777                ("let one = ", None),
 778                (";", Some(DiagnosticSeverity::ERROR)),
 779                ("\nlet two =", None),
 780                (" ", Some(DiagnosticSeverity::ERROR)),
 781                ("\nlet three = 3;\n", None)
 782            ]
 783        );
 784        buffer
 785    });
 786}
 787
 788#[gpui::test]
 789fn test_serialization(cx: &mut gpui::MutableAppContext) {
 790    let mut now = Instant::now();
 791
 792    let buffer1 = cx.add_model(|cx| {
 793        let mut buffer = Buffer::new(0, "abc", cx);
 794        buffer.edit([3..3], "D", cx);
 795
 796        now += Duration::from_secs(1);
 797        buffer.start_transaction_at(now);
 798        buffer.edit([4..4], "E", cx);
 799        buffer.end_transaction_at(now, cx);
 800        assert_eq!(buffer.text(), "abcDE");
 801
 802        buffer.undo(cx);
 803        assert_eq!(buffer.text(), "abcD");
 804
 805        buffer.edit([4..4], "F", cx);
 806        assert_eq!(buffer.text(), "abcDF");
 807        buffer
 808    });
 809    assert_eq!(buffer1.read(cx).text(), "abcDF");
 810
 811    let message = buffer1.read(cx).to_proto();
 812    let buffer2 = cx.add_model(|cx| Buffer::from_proto(1, message, None, cx).unwrap());
 813    assert_eq!(buffer2.read(cx).text(), "abcDF");
 814}
 815
 816#[gpui::test(iterations = 100)]
 817fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) {
 818    let min_peers = env::var("MIN_PEERS")
 819        .map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
 820        .unwrap_or(1);
 821    let max_peers = env::var("MAX_PEERS")
 822        .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
 823        .unwrap_or(5);
 824    let operations = env::var("OPERATIONS")
 825        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
 826        .unwrap_or(10);
 827
 828    let base_text_len = rng.gen_range(0..10);
 829    let base_text = RandomCharIter::new(&mut rng)
 830        .take(base_text_len)
 831        .collect::<String>();
 832    let mut replica_ids = Vec::new();
 833    let mut buffers = Vec::new();
 834    let mut network = Network::new(rng.clone());
 835
 836    for i in 0..rng.gen_range(min_peers..=max_peers) {
 837        let buffer = cx.add_model(|cx| {
 838            let mut buffer = Buffer::new(i as ReplicaId, base_text.as_str(), cx);
 839            buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
 840            buffer
 841        });
 842        buffers.push(buffer);
 843        replica_ids.push(i as ReplicaId);
 844        network.add_peer(i as ReplicaId);
 845        log::info!("Adding initial peer with replica id {}", i);
 846    }
 847
 848    log::info!("initial text: {:?}", base_text);
 849
 850    let mut now = Instant::now();
 851    let mut mutation_count = operations;
 852    let mut active_selections = BTreeMap::default();
 853    loop {
 854        let replica_index = rng.gen_range(0..replica_ids.len());
 855        let replica_id = replica_ids[replica_index];
 856        let buffer = &mut buffers[replica_index];
 857        let mut new_buffer = None;
 858        match rng.gen_range(0..100) {
 859            0..=29 if mutation_count != 0 => {
 860                buffer.update(cx, |buffer, cx| {
 861                    buffer.start_transaction_at(now);
 862                    buffer.randomly_edit(&mut rng, 5, cx);
 863                    buffer.end_transaction_at(now, cx);
 864                    log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
 865                });
 866                mutation_count -= 1;
 867            }
 868            30..=39 if mutation_count != 0 => {
 869                buffer.update(cx, |buffer, cx| {
 870                    let mut selections = Vec::new();
 871                    for id in 0..rng.gen_range(1..=5) {
 872                        let range = buffer.random_byte_range(0, &mut rng);
 873                        selections.push(Selection {
 874                            id,
 875                            start: buffer.anchor_before(range.start),
 876                            end: buffer.anchor_before(range.end),
 877                            reversed: false,
 878                            goal: SelectionGoal::None,
 879                        });
 880                    }
 881                    let selections: Arc<[Selection<Anchor>]> = selections.into();
 882                    log::info!(
 883                        "peer {} setting active selections: {:?}",
 884                        replica_id,
 885                        selections
 886                    );
 887                    active_selections.insert(replica_id, selections.clone());
 888                    buffer.set_active_selections(selections, cx);
 889                });
 890                mutation_count -= 1;
 891            }
 892            40..=49 if replica_ids.len() < max_peers => {
 893                let old_buffer = buffer.read(cx).to_proto();
 894                let new_replica_id = replica_ids.len() as ReplicaId;
 895                log::info!(
 896                    "Adding new replica {} (replicating from {})",
 897                    new_replica_id,
 898                    replica_id
 899                );
 900                new_buffer = Some(cx.add_model(|cx| {
 901                    let mut new_buffer =
 902                        Buffer::from_proto(new_replica_id, old_buffer, None, cx).unwrap();
 903                    new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
 904                    new_buffer
 905                }));
 906                replica_ids.push(new_replica_id);
 907                network.replicate(replica_id, new_replica_id);
 908            }
 909            50..=69 if mutation_count != 0 => {
 910                buffer.update(cx, |buffer, cx| {
 911                    buffer.randomly_undo_redo(&mut rng, cx);
 912                    log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
 913                });
 914                mutation_count -= 1;
 915            }
 916            70..=99 if network.has_unreceived(replica_id) => {
 917                let ops = network
 918                    .receive(replica_id)
 919                    .into_iter()
 920                    .map(|op| proto::deserialize_operation(op).unwrap());
 921                if ops.len() > 0 {
 922                    log::info!(
 923                        "peer {} applying {} ops from the network.",
 924                        replica_id,
 925                        ops.len()
 926                    );
 927                    buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx).unwrap());
 928                }
 929            }
 930            _ => {}
 931        }
 932
 933        buffer.update(cx, |buffer, _| {
 934            let ops = buffer
 935                .operations
 936                .drain(..)
 937                .map(|op| proto::serialize_operation(&op))
 938                .collect();
 939            network.broadcast(buffer.replica_id(), ops);
 940        });
 941        now += Duration::from_millis(rng.gen_range(0..=200));
 942        buffers.extend(new_buffer);
 943
 944        if mutation_count == 0 && network.is_idle() {
 945            break;
 946        }
 947    }
 948
 949    let first_buffer = buffers[0].read(cx);
 950    for buffer in &buffers[1..] {
 951        let buffer = buffer.read(cx);
 952        assert_eq!(
 953            buffer.text(),
 954            first_buffer.text(),
 955            "Replica {} text != Replica 0 text",
 956            buffer.replica_id()
 957        );
 958    }
 959
 960    for buffer in &buffers {
 961        let buffer = buffer.read(cx).snapshot();
 962        let expected_remote_selections = active_selections
 963            .iter()
 964            .filter(|(replica_id, _)| **replica_id != buffer.replica_id())
 965            .map(|(replica_id, selections)| (*replica_id, selections.iter().collect::<Vec<_>>()))
 966            .collect::<Vec<_>>();
 967        let actual_remote_selections = buffer
 968            .remote_selections_in_range(Anchor::min()..Anchor::max())
 969            .map(|(replica_id, selections)| (replica_id, selections.collect::<Vec<_>>()))
 970            .collect::<Vec<_>>();
 971        assert_eq!(actual_remote_selections, expected_remote_selections);
 972    }
 973}
 974
 975fn chunks_with_diagnostics<T: ToOffset + ToPoint>(
 976    buffer: &Buffer,
 977    range: Range<T>,
 978) -> Vec<(String, Option<DiagnosticSeverity>)> {
 979    let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = Vec::new();
 980    for chunk in buffer.snapshot().chunks(range, Some(&Default::default())) {
 981        if chunks
 982            .last()
 983            .map_or(false, |prev_chunk| prev_chunk.1 == chunk.diagnostic)
 984        {
 985            chunks.last_mut().unwrap().0.push_str(chunk.text);
 986        } else {
 987            chunks.push((chunk.text.to_string(), chunk.diagnostic));
 988        }
 989    }
 990    chunks
 991}
 992
 993#[test]
 994fn test_contiguous_ranges() {
 995    assert_eq!(
 996        contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::<Vec<_>>(),
 997        &[1..4, 5..7, 9..13]
 998    );
 999
1000    // Respects the `max_len` parameter
1001    assert_eq!(
1002        contiguous_ranges(
1003            [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(),
1004            3
1005        )
1006        .collect::<Vec<_>>(),
1007        &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
1008    );
1009}
1010
1011impl Buffer {
1012    pub fn enclosing_bracket_point_ranges<T: ToOffset>(
1013        &self,
1014        range: Range<T>,
1015    ) -> Option<(Range<Point>, Range<Point>)> {
1016        self.snapshot()
1017            .enclosing_bracket_ranges(range)
1018            .map(|(start, end)| {
1019                let point_start = start.start.to_point(self)..start.end.to_point(self);
1020                let point_end = end.start.to_point(self)..end.end.to_point(self);
1021                (point_start, point_end)
1022            })
1023    }
1024}
1025
1026fn rust_lang() -> Language {
1027    Language::new(
1028        LanguageConfig {
1029            name: "Rust".to_string(),
1030            path_suffixes: vec!["rs".to_string()],
1031            language_server: None,
1032            ..Default::default()
1033        },
1034        Some(tree_sitter_rust::language()),
1035    )
1036    .with_indents_query(
1037        r#"
1038                (call_expression) @indent
1039                (field_expression) @indent
1040                (_ "(" ")" @end) @indent
1041                (_ "{" "}" @end) @indent
1042            "#,
1043    )
1044    .unwrap()
1045    .with_brackets_query(r#" ("{" @open "}" @close) "#)
1046    .unwrap()
1047}
1048
1049fn empty(point: Point) -> Range<Point> {
1050    point..point
1051}