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                .map(|entry| (entry.range.to_point(buffer), &entry.diagnostic))
 537                .collect::<Vec<_>>(),
 538            &[
 539                (
 540                    Point::new(3, 9)..Point::new(3, 11),
 541                    &Diagnostic {
 542                        severity: DiagnosticSeverity::ERROR,
 543                        message: "undefined variable 'BB'".to_string(),
 544                        group_id: 1,
 545                        is_primary: true,
 546                    },
 547                ),
 548                (
 549                    Point::new(4, 9)..Point::new(4, 12),
 550                    &Diagnostic {
 551                        severity: DiagnosticSeverity::ERROR,
 552                        message: "undefined variable 'CCC'".to_string(),
 553                        group_id: 2,
 554                        is_primary: true,
 555                    }
 556                )
 557            ]
 558        );
 559        assert_eq!(
 560            chunks_with_diagnostics(buffer, 0..buffer.len()),
 561            [
 562                ("\n\nfn a() { ".to_string(), None),
 563                ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
 564                (" }\nfn b() { ".to_string(), None),
 565                ("BB".to_string(), Some(DiagnosticSeverity::ERROR)),
 566                (" }\nfn c() { ".to_string(), None),
 567                ("CCC".to_string(), Some(DiagnosticSeverity::ERROR)),
 568                (" }\n".to_string(), None),
 569            ]
 570        );
 571        assert_eq!(
 572            chunks_with_diagnostics(buffer, Point::new(3, 10)..Point::new(4, 11)),
 573            [
 574                ("B".to_string(), Some(DiagnosticSeverity::ERROR)),
 575                (" }\nfn c() { ".to_string(), None),
 576                ("CC".to_string(), Some(DiagnosticSeverity::ERROR)),
 577            ]
 578        );
 579
 580        // Ensure overlapping diagnostics are highlighted correctly.
 581        buffer
 582            .update_diagnostics(
 583                Some(open_notification.text_document.version),
 584                vec![
 585                    lsp::Diagnostic {
 586                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
 587                        severity: Some(lsp::DiagnosticSeverity::ERROR),
 588                        message: "undefined variable 'A'".to_string(),
 589                        ..Default::default()
 590                    },
 591                    lsp::Diagnostic {
 592                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 12)),
 593                        severity: Some(lsp::DiagnosticSeverity::WARNING),
 594                        message: "unreachable statement".to_string(),
 595                        ..Default::default()
 596                    },
 597                ],
 598                cx,
 599            )
 600            .unwrap();
 601        assert_eq!(
 602            buffer
 603                .diagnostics_in_range(Point::new(2, 0)..Point::new(3, 0))
 604                .map(|entry| (entry.range.to_point(buffer), &entry.diagnostic))
 605                .collect::<Vec<_>>(),
 606            &[
 607                (
 608                    Point::new(2, 9)..Point::new(2, 12),
 609                    &Diagnostic {
 610                        severity: DiagnosticSeverity::WARNING,
 611                        message: "unreachable statement".to_string(),
 612                        group_id: 1,
 613                        is_primary: true,
 614                    }
 615                ),
 616                (
 617                    Point::new(2, 9)..Point::new(2, 10),
 618                    &Diagnostic {
 619                        severity: DiagnosticSeverity::ERROR,
 620                        message: "undefined variable 'A'".to_string(),
 621                        group_id: 0,
 622                        is_primary: true,
 623                    },
 624                )
 625            ]
 626        );
 627        assert_eq!(
 628            chunks_with_diagnostics(buffer, Point::new(2, 0)..Point::new(3, 0)),
 629            [
 630                ("fn a() { ".to_string(), None),
 631                ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
 632                (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
 633                ("\n".to_string(), None),
 634            ]
 635        );
 636        assert_eq!(
 637            chunks_with_diagnostics(buffer, Point::new(2, 10)..Point::new(3, 0)),
 638            [
 639                (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
 640                ("\n".to_string(), None),
 641            ]
 642        );
 643    });
 644
 645    // Keep editing the buffer and ensure disk-based diagnostics get translated according to the
 646    // changes since the last save.
 647    buffer.update(&mut cx, |buffer, cx| {
 648        buffer.edit(Some(Point::new(2, 0)..Point::new(2, 0)), "    ", cx);
 649        buffer.edit(Some(Point::new(2, 8)..Point::new(2, 10)), "(x: usize)", cx);
 650    });
 651    let change_notification_2 = fake
 652        .receive_notification::<lsp::notification::DidChangeTextDocument>()
 653        .await;
 654    assert!(
 655        change_notification_2.text_document.version > change_notification_1.text_document.version
 656    );
 657
 658    buffer.update(&mut cx, |buffer, cx| {
 659        buffer
 660            .update_diagnostics(
 661                Some(change_notification_2.text_document.version),
 662                vec![
 663                    lsp::Diagnostic {
 664                        range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)),
 665                        severity: Some(lsp::DiagnosticSeverity::ERROR),
 666                        message: "undefined variable 'BB'".to_string(),
 667                        source: Some("disk".to_string()),
 668                        ..Default::default()
 669                    },
 670                    lsp::Diagnostic {
 671                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
 672                        severity: Some(lsp::DiagnosticSeverity::ERROR),
 673                        message: "undefined variable 'A'".to_string(),
 674                        source: Some("disk".to_string()),
 675                        ..Default::default()
 676                    },
 677                ],
 678                cx,
 679            )
 680            .unwrap();
 681        assert_eq!(
 682            buffer
 683                .diagnostics_in_range(0..buffer.len())
 684                .map(|entry| (entry.range.to_point(buffer), &entry.diagnostic))
 685                .collect::<Vec<_>>(),
 686            &[
 687                (
 688                    Point::new(2, 21)..Point::new(2, 22),
 689                    &Diagnostic {
 690                        severity: DiagnosticSeverity::ERROR,
 691                        message: "undefined variable 'A'".to_string(),
 692                        group_id: 0,
 693                        is_primary: true,
 694                    }
 695                ),
 696                (
 697                    Point::new(3, 9)..Point::new(3, 11),
 698                    &Diagnostic {
 699                        severity: DiagnosticSeverity::ERROR,
 700                        message: "undefined variable 'BB'".to_string(),
 701                        group_id: 1,
 702                        is_primary: true,
 703                    },
 704                )
 705            ]
 706        );
 707    });
 708}
 709
 710#[gpui::test]
 711async fn test_empty_diagnostic_ranges(mut cx: gpui::TestAppContext) {
 712    cx.add_model(|cx| {
 713        let text = concat!(
 714            "let one = ;\n", //
 715            "let two = \n",
 716            "let three = 3;\n",
 717        );
 718
 719        let mut buffer = Buffer::new(0, text, cx);
 720        buffer.set_language(Some(Arc::new(rust_lang())), None, cx);
 721        buffer
 722            .update_diagnostics(
 723                None,
 724                vec![
 725                    lsp::Diagnostic {
 726                        range: lsp::Range::new(
 727                            lsp::Position::new(0, 10),
 728                            lsp::Position::new(0, 10),
 729                        ),
 730                        severity: Some(lsp::DiagnosticSeverity::ERROR),
 731                        message: "syntax error 1".to_string(),
 732                        ..Default::default()
 733                    },
 734                    lsp::Diagnostic {
 735                        range: lsp::Range::new(
 736                            lsp::Position::new(1, 10),
 737                            lsp::Position::new(1, 10),
 738                        ),
 739                        severity: Some(lsp::DiagnosticSeverity::ERROR),
 740                        message: "syntax error 2".to_string(),
 741                        ..Default::default()
 742                    },
 743                ],
 744                cx,
 745            )
 746            .unwrap();
 747
 748        // An empty range is extended forward to include the following character.
 749        // At the end of a line, an empty range is extended backward to include
 750        // the preceding character.
 751        let chunks = chunks_with_diagnostics(&buffer, 0..buffer.len());
 752        assert_eq!(
 753            chunks
 754                .iter()
 755                .map(|(s, d)| (s.as_str(), *d))
 756                .collect::<Vec<_>>(),
 757            &[
 758                ("let one = ", None),
 759                (";", Some(lsp::DiagnosticSeverity::ERROR)),
 760                ("\nlet two =", None),
 761                (" ", Some(lsp::DiagnosticSeverity::ERROR)),
 762                ("\nlet three = 3;\n", None)
 763            ]
 764        );
 765        buffer
 766    });
 767}
 768
 769#[gpui::test]
 770async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) {
 771    cx.add_model(|cx| {
 772        let text = "
 773            fn foo(mut v: Vec<usize>) {
 774                for x in &v {
 775                    v.push(1);
 776                }
 777            }
 778        "
 779        .unindent();
 780
 781        let file = FakeFile::new("/example.rs");
 782        let mut buffer = Buffer::from_file(0, text, Box::new(file.clone()), cx);
 783        buffer.set_language(Some(Arc::new(rust_lang())), None, cx);
 784        let diagnostics = vec![
 785            lsp::Diagnostic {
 786                range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
 787                severity: Some(DiagnosticSeverity::WARNING),
 788                message: "error 1".to_string(),
 789                related_information: Some(vec![lsp::DiagnosticRelatedInformation {
 790                    location: lsp::Location {
 791                        uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
 792                        range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
 793                    },
 794                    message: "error 1 hint 1".to_string(),
 795                }]),
 796                ..Default::default()
 797            },
 798            lsp::Diagnostic {
 799                range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
 800                severity: Some(DiagnosticSeverity::HINT),
 801                message: "error 1 hint 1".to_string(),
 802                related_information: Some(vec![lsp::DiagnosticRelatedInformation {
 803                    location: lsp::Location {
 804                        uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
 805                        range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
 806                    },
 807                    message: "original diagnostic".to_string(),
 808                }]),
 809                ..Default::default()
 810            },
 811            lsp::Diagnostic {
 812                range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
 813                severity: Some(DiagnosticSeverity::ERROR),
 814                message: "error 2".to_string(),
 815                related_information: Some(vec![
 816                    lsp::DiagnosticRelatedInformation {
 817                        location: lsp::Location {
 818                            uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
 819                            range: lsp::Range::new(
 820                                lsp::Position::new(1, 13),
 821                                lsp::Position::new(1, 15),
 822                            ),
 823                        },
 824                        message: "error 2 hint 1".to_string(),
 825                    },
 826                    lsp::DiagnosticRelatedInformation {
 827                        location: lsp::Location {
 828                            uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
 829                            range: lsp::Range::new(
 830                                lsp::Position::new(1, 13),
 831                                lsp::Position::new(1, 15),
 832                            ),
 833                        },
 834                        message: "error 2 hint 2".to_string(),
 835                    },
 836                ]),
 837                ..Default::default()
 838            },
 839            lsp::Diagnostic {
 840                range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
 841                severity: Some(DiagnosticSeverity::HINT),
 842                message: "error 2 hint 1".to_string(),
 843                related_information: Some(vec![lsp::DiagnosticRelatedInformation {
 844                    location: lsp::Location {
 845                        uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
 846                        range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
 847                    },
 848                    message: "original diagnostic".to_string(),
 849                }]),
 850                ..Default::default()
 851            },
 852            lsp::Diagnostic {
 853                range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
 854                severity: Some(DiagnosticSeverity::HINT),
 855                message: "error 2 hint 2".to_string(),
 856                related_information: Some(vec![lsp::DiagnosticRelatedInformation {
 857                    location: lsp::Location {
 858                        uri: lsp::Url::from_file_path(&file.abs_path).unwrap(),
 859                        range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
 860                    },
 861                    message: "original diagnostic".to_string(),
 862                }]),
 863                ..Default::default()
 864            },
 865        ];
 866        buffer.update_diagnostics(None, diagnostics, cx).unwrap();
 867        assert_eq!(
 868            buffer
 869                .diagnostics_in_range(0..buffer.len())
 870                .map(|entry| (entry.range.to_point(&buffer), &entry.diagnostic))
 871                .collect::<Vec<_>>(),
 872            &[
 873                (
 874                    Point::new(1, 8)..Point::new(1, 9),
 875                    &Diagnostic {
 876                        severity: DiagnosticSeverity::WARNING,
 877                        message: "error 1".to_string(),
 878                        group_id: 0,
 879                        is_primary: true,
 880                    }
 881                ),
 882                (
 883                    Point::new(1, 8)..Point::new(1, 9),
 884                    &Diagnostic {
 885                        severity: DiagnosticSeverity::HINT,
 886                        message: "error 1 hint 1".to_string(),
 887                        group_id: 0,
 888                        is_primary: false,
 889                    }
 890                ),
 891                (
 892                    Point::new(1, 13)..Point::new(1, 15),
 893                    &Diagnostic {
 894                        severity: DiagnosticSeverity::HINT,
 895                        message: "error 2 hint 1".to_string(),
 896                        group_id: 1,
 897                        is_primary: false,
 898                    }
 899                ),
 900                (
 901                    Point::new(1, 13)..Point::new(1, 15),
 902                    &Diagnostic {
 903                        severity: DiagnosticSeverity::HINT,
 904                        message: "error 2 hint 2".to_string(),
 905                        group_id: 1,
 906                        is_primary: false,
 907                    }
 908                ),
 909                (
 910                    Point::new(2, 8)..Point::new(2, 17),
 911                    &Diagnostic {
 912                        severity: DiagnosticSeverity::ERROR,
 913                        message: "error 2".to_string(),
 914                        group_id: 1,
 915                        is_primary: true,
 916                    }
 917                )
 918            ]
 919        );
 920
 921        assert_eq!(
 922            buffer
 923                .diagnostic_group(0)
 924                .map(|entry| (entry.range.to_point(&buffer), &entry.diagnostic))
 925                .collect::<Vec<_>>(),
 926            &[
 927                (
 928                    Point::new(1, 8)..Point::new(1, 9),
 929                    &Diagnostic {
 930                        severity: DiagnosticSeverity::WARNING,
 931                        message: "error 1".to_string(),
 932                        group_id: 0,
 933                        is_primary: true,
 934                    }
 935                ),
 936                (
 937                    Point::new(1, 8)..Point::new(1, 9),
 938                    &Diagnostic {
 939                        severity: DiagnosticSeverity::HINT,
 940                        message: "error 1 hint 1".to_string(),
 941                        group_id: 0,
 942                        is_primary: false,
 943                    }
 944                ),
 945            ]
 946        );
 947        assert_eq!(
 948            buffer
 949                .diagnostic_group(1)
 950                .map(|entry| (entry.range.to_point(&buffer), &entry.diagnostic))
 951                .collect::<Vec<_>>(),
 952            &[
 953                (
 954                    Point::new(1, 13)..Point::new(1, 15),
 955                    &Diagnostic {
 956                        severity: DiagnosticSeverity::HINT,
 957                        message: "error 2 hint 1".to_string(),
 958                        group_id: 1,
 959                        is_primary: false,
 960                    }
 961                ),
 962                (
 963                    Point::new(1, 13)..Point::new(1, 15),
 964                    &Diagnostic {
 965                        severity: DiagnosticSeverity::HINT,
 966                        message: "error 2 hint 2".to_string(),
 967                        group_id: 1,
 968                        is_primary: false,
 969                    }
 970                ),
 971                (
 972                    Point::new(2, 8)..Point::new(2, 17),
 973                    &Diagnostic {
 974                        severity: DiagnosticSeverity::ERROR,
 975                        message: "error 2".to_string(),
 976                        group_id: 1,
 977                        is_primary: true,
 978                    }
 979                )
 980            ]
 981        );
 982
 983        buffer
 984    });
 985}
 986
 987fn chunks_with_diagnostics<T: ToOffset + ToPoint>(
 988    buffer: &Buffer,
 989    range: Range<T>,
 990) -> Vec<(String, Option<DiagnosticSeverity>)> {
 991    let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = Vec::new();
 992    for chunk in buffer.snapshot().chunks(range, Some(&Default::default())) {
 993        if chunks
 994            .last()
 995            .map_or(false, |prev_chunk| prev_chunk.1 == chunk.diagnostic)
 996        {
 997            chunks.last_mut().unwrap().0.push_str(chunk.text);
 998        } else {
 999            chunks.push((chunk.text.to_string(), chunk.diagnostic));
1000        }
1001    }
1002    chunks
1003}
1004
1005#[test]
1006fn test_contiguous_ranges() {
1007    assert_eq!(
1008        contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::<Vec<_>>(),
1009        &[1..4, 5..7, 9..13]
1010    );
1011
1012    // Respects the `max_len` parameter
1013    assert_eq!(
1014        contiguous_ranges(
1015            [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(),
1016            3
1017        )
1018        .collect::<Vec<_>>(),
1019        &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
1020    );
1021}
1022
1023impl Buffer {
1024    pub fn enclosing_bracket_point_ranges<T: ToOffset>(
1025        &self,
1026        range: Range<T>,
1027    ) -> Option<(Range<Point>, Range<Point>)> {
1028        self.enclosing_bracket_ranges(range).map(|(start, end)| {
1029            let point_start = start.start.to_point(self)..start.end.to_point(self);
1030            let point_end = end.start.to_point(self)..end.end.to_point(self);
1031            (point_start, point_end)
1032        })
1033    }
1034}
1035
1036fn rust_lang() -> Language {
1037    Language::new(
1038        LanguageConfig {
1039            name: "Rust".to_string(),
1040            path_suffixes: vec!["rs".to_string()],
1041            language_server: None,
1042            ..Default::default()
1043        },
1044        Some(tree_sitter_rust::language()),
1045    )
1046    .with_indents_query(
1047        r#"
1048                (call_expression) @indent
1049                (field_expression) @indent
1050                (_ "(" ")" @end) @indent
1051                (_ "{" "}" @end) @indent
1052            "#,
1053    )
1054    .unwrap()
1055    .with_brackets_query(r#" ("{" @open "}" @close) "#)
1056    .unwrap()
1057}
1058
1059fn empty(point: Point) -> Range<Point> {
1060    point..point
1061}
1062
1063#[derive(Clone)]
1064struct FakeFile {
1065    abs_path: PathBuf,
1066}
1067
1068impl FakeFile {
1069    fn new(abs_path: impl Into<PathBuf>) -> Self {
1070        Self {
1071            abs_path: abs_path.into(),
1072        }
1073    }
1074}
1075
1076impl File for FakeFile {
1077    fn worktree_id(&self) -> usize {
1078        todo!()
1079    }
1080
1081    fn entry_id(&self) -> Option<usize> {
1082        todo!()
1083    }
1084
1085    fn mtime(&self) -> SystemTime {
1086        SystemTime::now()
1087    }
1088
1089    fn path(&self) -> &Arc<Path> {
1090        todo!()
1091    }
1092
1093    fn abs_path(&self) -> Option<PathBuf> {
1094        Some(self.abs_path.clone())
1095    }
1096
1097    fn full_path(&self) -> PathBuf {
1098        todo!()
1099    }
1100
1101    fn file_name(&self) -> Option<OsString> {
1102        todo!()
1103    }
1104
1105    fn is_deleted(&self) -> bool {
1106        todo!()
1107    }
1108
1109    fn save(
1110        &self,
1111        _: u64,
1112        _: Rope,
1113        _: clock::Global,
1114        _: &mut MutableAppContext,
1115    ) -> Task<Result<(clock::Global, SystemTime)>> {
1116        todo!()
1117    }
1118
1119    fn load_local(&self, _: &AppContext) -> Option<Task<Result<String>>> {
1120        todo!()
1121    }
1122
1123    fn buffer_updated(&self, _: u64, _: super::Operation, _: &mut MutableAppContext) {
1124        todo!()
1125    }
1126
1127    fn buffer_removed(&self, _: u64, _: &mut MutableAppContext) {
1128        todo!()
1129    }
1130
1131    fn boxed_clone(&self) -> Box<dyn File> {
1132        todo!()
1133    }
1134
1135    fn as_any(&self) -> &dyn Any {
1136        todo!()
1137    }
1138}