tests.rs

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