tests.rs

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