project_tests.rs

   1use crate::{worktree::WorktreeHandle, Event, *};
   2use fs::LineEnding;
   3use fs::{FakeFs, RealFs};
   4use futures::{future, StreamExt};
   5use gpui::{executor::Deterministic, test::subscribe};
   6use language::{
   7    tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig,
   8    OffsetRangeExt, Point, ToPoint,
   9};
  10use lsp::Url;
  11use parking_lot::Mutex;
  12use pretty_assertions::assert_eq;
  13use serde_json::json;
  14use std::{cell::RefCell, os::unix, rc::Rc, task::Poll};
  15use unindent::Unindent as _;
  16use util::{assert_set_eq, test::temp_tree};
  17
  18#[cfg(test)]
  19#[ctor::ctor]
  20fn init_logger() {
  21    if std::env::var("RUST_LOG").is_ok() {
  22        env_logger::init();
  23    }
  24}
  25
  26#[gpui::test]
  27async fn test_symlinks(cx: &mut gpui::TestAppContext) {
  28    let dir = temp_tree(json!({
  29        "root": {
  30            "apple": "",
  31            "banana": {
  32                "carrot": {
  33                    "date": "",
  34                    "endive": "",
  35                }
  36            },
  37            "fennel": {
  38                "grape": "",
  39            }
  40        }
  41    }));
  42
  43    let root_link_path = dir.path().join("root_link");
  44    unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
  45    unix::fs::symlink(
  46        &dir.path().join("root/fennel"),
  47        &dir.path().join("root/finnochio"),
  48    )
  49    .unwrap();
  50
  51    let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await;
  52    project.read_with(cx, |project, cx| {
  53        let tree = project.worktrees(cx).next().unwrap().read(cx);
  54        assert_eq!(tree.file_count(), 5);
  55        assert_eq!(
  56            tree.inode_for_path("fennel/grape"),
  57            tree.inode_for_path("finnochio/grape")
  58        );
  59    });
  60}
  61
  62#[gpui::test]
  63async fn test_managing_language_servers(
  64    deterministic: Arc<Deterministic>,
  65    cx: &mut gpui::TestAppContext,
  66) {
  67    cx.foreground().forbid_parking();
  68
  69    let mut rust_language = Language::new(
  70        LanguageConfig {
  71            name: "Rust".into(),
  72            path_suffixes: vec!["rs".to_string()],
  73            ..Default::default()
  74        },
  75        Some(tree_sitter_rust::language()),
  76    );
  77    let mut json_language = Language::new(
  78        LanguageConfig {
  79            name: "JSON".into(),
  80            path_suffixes: vec!["json".to_string()],
  81            ..Default::default()
  82        },
  83        None,
  84    );
  85    let mut fake_rust_servers = rust_language
  86        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
  87            name: "the-rust-language-server",
  88            capabilities: lsp::ServerCapabilities {
  89                completion_provider: Some(lsp::CompletionOptions {
  90                    trigger_characters: Some(vec![".".to_string(), "::".to_string()]),
  91                    ..Default::default()
  92                }),
  93                ..Default::default()
  94            },
  95            ..Default::default()
  96        }))
  97        .await;
  98    let mut fake_json_servers = json_language
  99        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
 100            name: "the-json-language-server",
 101            capabilities: lsp::ServerCapabilities {
 102                completion_provider: Some(lsp::CompletionOptions {
 103                    trigger_characters: Some(vec![":".to_string()]),
 104                    ..Default::default()
 105                }),
 106                ..Default::default()
 107            },
 108            ..Default::default()
 109        }))
 110        .await;
 111
 112    let fs = FakeFs::new(cx.background());
 113    fs.insert_tree(
 114        "/the-root",
 115        json!({
 116            "test.rs": "const A: i32 = 1;",
 117            "test2.rs": "",
 118            "Cargo.toml": "a = 1",
 119            "package.json": "{\"a\": 1}",
 120        }),
 121    )
 122    .await;
 123
 124    let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
 125
 126    // Open a buffer without an associated language server.
 127    let toml_buffer = project
 128        .update(cx, |project, cx| {
 129            project.open_local_buffer("/the-root/Cargo.toml", cx)
 130        })
 131        .await
 132        .unwrap();
 133
 134    // Open a buffer with an associated language server before the language for it has been loaded.
 135    let rust_buffer = project
 136        .update(cx, |project, cx| {
 137            project.open_local_buffer("/the-root/test.rs", cx)
 138        })
 139        .await
 140        .unwrap();
 141    rust_buffer.read_with(cx, |buffer, _| {
 142        assert_eq!(buffer.language().map(|l| l.name()), None);
 143    });
 144
 145    // Now we add the languages to the project, and ensure they get assigned to all
 146    // the relevant open buffers.
 147    project.update(cx, |project, _| {
 148        project.languages.add(Arc::new(json_language));
 149        project.languages.add(Arc::new(rust_language));
 150    });
 151    deterministic.run_until_parked();
 152    rust_buffer.read_with(cx, |buffer, _| {
 153        assert_eq!(buffer.language().map(|l| l.name()), Some("Rust".into()));
 154    });
 155
 156    // A server is started up, and it is notified about Rust files.
 157    let mut fake_rust_server = fake_rust_servers.next().await.unwrap();
 158    assert_eq!(
 159        fake_rust_server
 160            .receive_notification::<lsp::notification::DidOpenTextDocument>()
 161            .await
 162            .text_document,
 163        lsp::TextDocumentItem {
 164            uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(),
 165            version: 0,
 166            text: "const A: i32 = 1;".to_string(),
 167            language_id: Default::default()
 168        }
 169    );
 170
 171    // The buffer is configured based on the language server's capabilities.
 172    rust_buffer.read_with(cx, |buffer, _| {
 173        assert_eq!(
 174            buffer.completion_triggers(),
 175            &[".".to_string(), "::".to_string()]
 176        );
 177    });
 178    toml_buffer.read_with(cx, |buffer, _| {
 179        assert!(buffer.completion_triggers().is_empty());
 180    });
 181
 182    // Edit a buffer. The changes are reported to the language server.
 183    rust_buffer.update(cx, |buffer, cx| buffer.edit([(16..16, "2")], None, cx));
 184    assert_eq!(
 185        fake_rust_server
 186            .receive_notification::<lsp::notification::DidChangeTextDocument>()
 187            .await
 188            .text_document,
 189        lsp::VersionedTextDocumentIdentifier::new(
 190            lsp::Url::from_file_path("/the-root/test.rs").unwrap(),
 191            1
 192        )
 193    );
 194
 195    // Open a third buffer with a different associated language server.
 196    let json_buffer = project
 197        .update(cx, |project, cx| {
 198            project.open_local_buffer("/the-root/package.json", cx)
 199        })
 200        .await
 201        .unwrap();
 202
 203    // A json language server is started up and is only notified about the json buffer.
 204    let mut fake_json_server = fake_json_servers.next().await.unwrap();
 205    assert_eq!(
 206        fake_json_server
 207            .receive_notification::<lsp::notification::DidOpenTextDocument>()
 208            .await
 209            .text_document,
 210        lsp::TextDocumentItem {
 211            uri: lsp::Url::from_file_path("/the-root/package.json").unwrap(),
 212            version: 0,
 213            text: "{\"a\": 1}".to_string(),
 214            language_id: Default::default()
 215        }
 216    );
 217
 218    // This buffer is configured based on the second language server's
 219    // capabilities.
 220    json_buffer.read_with(cx, |buffer, _| {
 221        assert_eq!(buffer.completion_triggers(), &[":".to_string()]);
 222    });
 223
 224    // When opening another buffer whose language server is already running,
 225    // it is also configured based on the existing language server's capabilities.
 226    let rust_buffer2 = project
 227        .update(cx, |project, cx| {
 228            project.open_local_buffer("/the-root/test2.rs", cx)
 229        })
 230        .await
 231        .unwrap();
 232    rust_buffer2.read_with(cx, |buffer, _| {
 233        assert_eq!(
 234            buffer.completion_triggers(),
 235            &[".".to_string(), "::".to_string()]
 236        );
 237    });
 238
 239    // Changes are reported only to servers matching the buffer's language.
 240    toml_buffer.update(cx, |buffer, cx| buffer.edit([(5..5, "23")], None, cx));
 241    rust_buffer2.update(cx, |buffer, cx| {
 242        buffer.edit([(0..0, "let x = 1;")], None, cx)
 243    });
 244    assert_eq!(
 245        fake_rust_server
 246            .receive_notification::<lsp::notification::DidChangeTextDocument>()
 247            .await
 248            .text_document,
 249        lsp::VersionedTextDocumentIdentifier::new(
 250            lsp::Url::from_file_path("/the-root/test2.rs").unwrap(),
 251            1
 252        )
 253    );
 254
 255    // Save notifications are reported to all servers.
 256    project
 257        .update(cx, |project, cx| project.save_buffer(toml_buffer, cx))
 258        .await
 259        .unwrap();
 260    assert_eq!(
 261        fake_rust_server
 262            .receive_notification::<lsp::notification::DidSaveTextDocument>()
 263            .await
 264            .text_document,
 265        lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path("/the-root/Cargo.toml").unwrap())
 266    );
 267    assert_eq!(
 268        fake_json_server
 269            .receive_notification::<lsp::notification::DidSaveTextDocument>()
 270            .await
 271            .text_document,
 272        lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path("/the-root/Cargo.toml").unwrap())
 273    );
 274
 275    // Renames are reported only to servers matching the buffer's language.
 276    fs.rename(
 277        Path::new("/the-root/test2.rs"),
 278        Path::new("/the-root/test3.rs"),
 279        Default::default(),
 280    )
 281    .await
 282    .unwrap();
 283    assert_eq!(
 284        fake_rust_server
 285            .receive_notification::<lsp::notification::DidCloseTextDocument>()
 286            .await
 287            .text_document,
 288        lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path("/the-root/test2.rs").unwrap()),
 289    );
 290    assert_eq!(
 291        fake_rust_server
 292            .receive_notification::<lsp::notification::DidOpenTextDocument>()
 293            .await
 294            .text_document,
 295        lsp::TextDocumentItem {
 296            uri: lsp::Url::from_file_path("/the-root/test3.rs").unwrap(),
 297            version: 0,
 298            text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()),
 299            language_id: Default::default()
 300        },
 301    );
 302
 303    rust_buffer2.update(cx, |buffer, cx| {
 304        buffer.update_diagnostics(
 305            DiagnosticSet::from_sorted_entries(
 306                vec![DiagnosticEntry {
 307                    diagnostic: Default::default(),
 308                    range: Anchor::MIN..Anchor::MAX,
 309                }],
 310                &buffer.snapshot(),
 311            ),
 312            cx,
 313        );
 314        assert_eq!(
 315            buffer
 316                .snapshot()
 317                .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
 318                .count(),
 319            1
 320        );
 321    });
 322
 323    // When the rename changes the extension of the file, the buffer gets closed on the old
 324    // language server and gets opened on the new one.
 325    fs.rename(
 326        Path::new("/the-root/test3.rs"),
 327        Path::new("/the-root/test3.json"),
 328        Default::default(),
 329    )
 330    .await
 331    .unwrap();
 332    assert_eq!(
 333        fake_rust_server
 334            .receive_notification::<lsp::notification::DidCloseTextDocument>()
 335            .await
 336            .text_document,
 337        lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path("/the-root/test3.rs").unwrap(),),
 338    );
 339    assert_eq!(
 340        fake_json_server
 341            .receive_notification::<lsp::notification::DidOpenTextDocument>()
 342            .await
 343            .text_document,
 344        lsp::TextDocumentItem {
 345            uri: lsp::Url::from_file_path("/the-root/test3.json").unwrap(),
 346            version: 0,
 347            text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()),
 348            language_id: Default::default()
 349        },
 350    );
 351
 352    // We clear the diagnostics, since the language has changed.
 353    rust_buffer2.read_with(cx, |buffer, _| {
 354        assert_eq!(
 355            buffer
 356                .snapshot()
 357                .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
 358                .count(),
 359            0
 360        );
 361    });
 362
 363    // The renamed file's version resets after changing language server.
 364    rust_buffer2.update(cx, |buffer, cx| buffer.edit([(0..0, "// ")], None, cx));
 365    assert_eq!(
 366        fake_json_server
 367            .receive_notification::<lsp::notification::DidChangeTextDocument>()
 368            .await
 369            .text_document,
 370        lsp::VersionedTextDocumentIdentifier::new(
 371            lsp::Url::from_file_path("/the-root/test3.json").unwrap(),
 372            1
 373        )
 374    );
 375
 376    // Restart language servers
 377    project.update(cx, |project, cx| {
 378        project.restart_language_servers_for_buffers(
 379            vec![rust_buffer.clone(), json_buffer.clone()],
 380            cx,
 381        );
 382    });
 383
 384    let mut rust_shutdown_requests = fake_rust_server
 385        .handle_request::<lsp::request::Shutdown, _, _>(|_, _| future::ready(Ok(())));
 386    let mut json_shutdown_requests = fake_json_server
 387        .handle_request::<lsp::request::Shutdown, _, _>(|_, _| future::ready(Ok(())));
 388    futures::join!(rust_shutdown_requests.next(), json_shutdown_requests.next());
 389
 390    let mut fake_rust_server = fake_rust_servers.next().await.unwrap();
 391    let mut fake_json_server = fake_json_servers.next().await.unwrap();
 392
 393    // Ensure rust document is reopened in new rust language server
 394    assert_eq!(
 395        fake_rust_server
 396            .receive_notification::<lsp::notification::DidOpenTextDocument>()
 397            .await
 398            .text_document,
 399        lsp::TextDocumentItem {
 400            uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(),
 401            version: 1,
 402            text: rust_buffer.read_with(cx, |buffer, _| buffer.text()),
 403            language_id: Default::default()
 404        }
 405    );
 406
 407    // Ensure json documents are reopened in new json language server
 408    assert_set_eq!(
 409        [
 410            fake_json_server
 411                .receive_notification::<lsp::notification::DidOpenTextDocument>()
 412                .await
 413                .text_document,
 414            fake_json_server
 415                .receive_notification::<lsp::notification::DidOpenTextDocument>()
 416                .await
 417                .text_document,
 418        ],
 419        [
 420            lsp::TextDocumentItem {
 421                uri: lsp::Url::from_file_path("/the-root/package.json").unwrap(),
 422                version: 0,
 423                text: json_buffer.read_with(cx, |buffer, _| buffer.text()),
 424                language_id: Default::default()
 425            },
 426            lsp::TextDocumentItem {
 427                uri: lsp::Url::from_file_path("/the-root/test3.json").unwrap(),
 428                version: 1,
 429                text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()),
 430                language_id: Default::default()
 431            }
 432        ]
 433    );
 434
 435    // Close notifications are reported only to servers matching the buffer's language.
 436    cx.update(|_| drop(json_buffer));
 437    let close_message = lsp::DidCloseTextDocumentParams {
 438        text_document: lsp::TextDocumentIdentifier::new(
 439            lsp::Url::from_file_path("/the-root/package.json").unwrap(),
 440        ),
 441    };
 442    assert_eq!(
 443        fake_json_server
 444            .receive_notification::<lsp::notification::DidCloseTextDocument>()
 445            .await,
 446        close_message,
 447    );
 448}
 449
 450#[gpui::test]
 451async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppContext) {
 452    cx.foreground().forbid_parking();
 453
 454    let mut language = Language::new(
 455        LanguageConfig {
 456            name: "Rust".into(),
 457            path_suffixes: vec!["rs".to_string()],
 458            ..Default::default()
 459        },
 460        Some(tree_sitter_rust::language()),
 461    );
 462    let mut fake_servers = language
 463        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
 464            name: "the-language-server",
 465            ..Default::default()
 466        }))
 467        .await;
 468
 469    let fs = FakeFs::new(cx.background());
 470    fs.insert_tree(
 471        "/the-root",
 472        json!({
 473            "a.rs": "",
 474            "b.rs": "",
 475        }),
 476    )
 477    .await;
 478
 479    let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
 480    project.update(cx, |project, _| {
 481        project.languages.add(Arc::new(language));
 482    });
 483    cx.foreground().run_until_parked();
 484
 485    // Start the language server by opening a buffer with a compatible file extension.
 486    let _buffer = project
 487        .update(cx, |project, cx| {
 488            project.open_local_buffer("/the-root/a.rs", cx)
 489        })
 490        .await
 491        .unwrap();
 492
 493    // Keep track of the FS events reported to the language server.
 494    let fake_server = fake_servers.next().await.unwrap();
 495    let file_changes = Arc::new(Mutex::new(Vec::new()));
 496    fake_server
 497        .request::<lsp::request::RegisterCapability>(lsp::RegistrationParams {
 498            registrations: vec![lsp::Registration {
 499                id: Default::default(),
 500                method: "workspace/didChangeWatchedFiles".to_string(),
 501                register_options: serde_json::to_value(
 502                    lsp::DidChangeWatchedFilesRegistrationOptions {
 503                        watchers: vec![lsp::FileSystemWatcher {
 504                            glob_pattern: "*.{rs,c}".to_string(),
 505                            kind: None,
 506                        }],
 507                    },
 508                )
 509                .ok(),
 510            }],
 511        })
 512        .await
 513        .unwrap();
 514    fake_server.handle_notification::<lsp::notification::DidChangeWatchedFiles, _>({
 515        let file_changes = file_changes.clone();
 516        move |params, _| {
 517            let mut file_changes = file_changes.lock();
 518            file_changes.extend(params.changes);
 519            file_changes.sort_by(|a, b| a.uri.cmp(&b.uri));
 520        }
 521    });
 522
 523    cx.foreground().run_until_parked();
 524    assert_eq!(file_changes.lock().len(), 0);
 525
 526    // Perform some file system mutations, two of which match the watched patterns,
 527    // and one of which does not.
 528    fs.create_file("/the-root/c.rs".as_ref(), Default::default())
 529        .await
 530        .unwrap();
 531    fs.create_file("/the-root/d.txt".as_ref(), Default::default())
 532        .await
 533        .unwrap();
 534    fs.remove_file("/the-root/b.rs".as_ref(), Default::default())
 535        .await
 536        .unwrap();
 537
 538    // The language server receives events for the FS mutations that match its watch patterns.
 539    cx.foreground().run_until_parked();
 540    assert_eq!(
 541        &*file_changes.lock(),
 542        &[
 543            lsp::FileEvent {
 544                uri: lsp::Url::from_file_path("/the-root/b.rs").unwrap(),
 545                typ: lsp::FileChangeType::DELETED,
 546            },
 547            lsp::FileEvent {
 548                uri: lsp::Url::from_file_path("/the-root/c.rs").unwrap(),
 549                typ: lsp::FileChangeType::CREATED,
 550            },
 551        ]
 552    );
 553}
 554
 555#[gpui::test]
 556async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
 557    cx.foreground().forbid_parking();
 558
 559    let fs = FakeFs::new(cx.background());
 560    fs.insert_tree(
 561        "/dir",
 562        json!({
 563            "a.rs": "let a = 1;",
 564            "b.rs": "let b = 2;"
 565        }),
 566    )
 567    .await;
 568
 569    let project = Project::test(fs, ["/dir/a.rs".as_ref(), "/dir/b.rs".as_ref()], cx).await;
 570
 571    let buffer_a = project
 572        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
 573        .await
 574        .unwrap();
 575    let buffer_b = project
 576        .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx))
 577        .await
 578        .unwrap();
 579
 580    project.update(cx, |project, cx| {
 581        project
 582            .update_diagnostics(
 583                0,
 584                lsp::PublishDiagnosticsParams {
 585                    uri: Url::from_file_path("/dir/a.rs").unwrap(),
 586                    version: None,
 587                    diagnostics: vec![lsp::Diagnostic {
 588                        range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 5)),
 589                        severity: Some(lsp::DiagnosticSeverity::ERROR),
 590                        message: "error 1".to_string(),
 591                        ..Default::default()
 592                    }],
 593                },
 594                &[],
 595                cx,
 596            )
 597            .unwrap();
 598        project
 599            .update_diagnostics(
 600                0,
 601                lsp::PublishDiagnosticsParams {
 602                    uri: Url::from_file_path("/dir/b.rs").unwrap(),
 603                    version: None,
 604                    diagnostics: vec![lsp::Diagnostic {
 605                        range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 5)),
 606                        severity: Some(lsp::DiagnosticSeverity::WARNING),
 607                        message: "error 2".to_string(),
 608                        ..Default::default()
 609                    }],
 610                },
 611                &[],
 612                cx,
 613            )
 614            .unwrap();
 615    });
 616
 617    buffer_a.read_with(cx, |buffer, _| {
 618        let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
 619        assert_eq!(
 620            chunks
 621                .iter()
 622                .map(|(s, d)| (s.as_str(), *d))
 623                .collect::<Vec<_>>(),
 624            &[
 625                ("let ", None),
 626                ("a", Some(DiagnosticSeverity::ERROR)),
 627                (" = 1;", None),
 628            ]
 629        );
 630    });
 631    buffer_b.read_with(cx, |buffer, _| {
 632        let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
 633        assert_eq!(
 634            chunks
 635                .iter()
 636                .map(|(s, d)| (s.as_str(), *d))
 637                .collect::<Vec<_>>(),
 638            &[
 639                ("let ", None),
 640                ("b", Some(DiagnosticSeverity::WARNING)),
 641                (" = 2;", None),
 642            ]
 643        );
 644    });
 645}
 646
 647#[gpui::test]
 648async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
 649    cx.foreground().forbid_parking();
 650
 651    let fs = FakeFs::new(cx.background());
 652    fs.insert_tree(
 653        "/root",
 654        json!({
 655            "dir": {
 656                "a.rs": "let a = 1;",
 657            },
 658            "other.rs": "let b = c;"
 659        }),
 660    )
 661    .await;
 662
 663    let project = Project::test(fs, ["/root/dir".as_ref()], cx).await;
 664
 665    let (worktree, _) = project
 666        .update(cx, |project, cx| {
 667            project.find_or_create_local_worktree("/root/other.rs", false, cx)
 668        })
 669        .await
 670        .unwrap();
 671    let worktree_id = worktree.read_with(cx, |tree, _| tree.id());
 672
 673    project.update(cx, |project, cx| {
 674        project
 675            .update_diagnostics(
 676                0,
 677                lsp::PublishDiagnosticsParams {
 678                    uri: Url::from_file_path("/root/other.rs").unwrap(),
 679                    version: None,
 680                    diagnostics: vec![lsp::Diagnostic {
 681                        range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 9)),
 682                        severity: Some(lsp::DiagnosticSeverity::ERROR),
 683                        message: "unknown variable 'c'".to_string(),
 684                        ..Default::default()
 685                    }],
 686                },
 687                &[],
 688                cx,
 689            )
 690            .unwrap();
 691    });
 692
 693    let buffer = project
 694        .update(cx, |project, cx| project.open_buffer((worktree_id, ""), cx))
 695        .await
 696        .unwrap();
 697    buffer.read_with(cx, |buffer, _| {
 698        let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
 699        assert_eq!(
 700            chunks
 701                .iter()
 702                .map(|(s, d)| (s.as_str(), *d))
 703                .collect::<Vec<_>>(),
 704            &[
 705                ("let b = ", None),
 706                ("c", Some(DiagnosticSeverity::ERROR)),
 707                (";", None),
 708            ]
 709        );
 710    });
 711
 712    project.read_with(cx, |project, cx| {
 713        assert_eq!(project.diagnostic_summaries(cx).next(), None);
 714        assert_eq!(project.diagnostic_summary(cx).error_count, 0);
 715    });
 716}
 717
 718#[gpui::test]
 719async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
 720    cx.foreground().forbid_parking();
 721
 722    let progress_token = "the-progress-token";
 723    let mut language = Language::new(
 724        LanguageConfig {
 725            name: "Rust".into(),
 726            path_suffixes: vec!["rs".to_string()],
 727            ..Default::default()
 728        },
 729        Some(tree_sitter_rust::language()),
 730    );
 731    let mut fake_servers = language
 732        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
 733            disk_based_diagnostics_progress_token: Some(progress_token.into()),
 734            disk_based_diagnostics_sources: vec!["disk".into()],
 735            ..Default::default()
 736        }))
 737        .await;
 738
 739    let fs = FakeFs::new(cx.background());
 740    fs.insert_tree(
 741        "/dir",
 742        json!({
 743            "a.rs": "fn a() { A }",
 744            "b.rs": "const y: i32 = 1",
 745        }),
 746    )
 747    .await;
 748
 749    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
 750    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
 751    let worktree_id = project.read_with(cx, |p, cx| p.worktrees(cx).next().unwrap().read(cx).id());
 752
 753    // Cause worktree to start the fake language server
 754    let _buffer = project
 755        .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx))
 756        .await
 757        .unwrap();
 758
 759    let mut events = subscribe(&project, cx);
 760
 761    let fake_server = fake_servers.next().await.unwrap();
 762    fake_server
 763        .start_progress(format!("{}/0", progress_token))
 764        .await;
 765    assert_eq!(
 766        events.next().await.unwrap(),
 767        Event::DiskBasedDiagnosticsStarted {
 768            language_server_id: 0,
 769        }
 770    );
 771
 772    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
 773        uri: Url::from_file_path("/dir/a.rs").unwrap(),
 774        version: None,
 775        diagnostics: vec![lsp::Diagnostic {
 776            range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
 777            severity: Some(lsp::DiagnosticSeverity::ERROR),
 778            message: "undefined variable 'A'".to_string(),
 779            ..Default::default()
 780        }],
 781    });
 782    assert_eq!(
 783        events.next().await.unwrap(),
 784        Event::DiagnosticsUpdated {
 785            language_server_id: 0,
 786            path: (worktree_id, Path::new("a.rs")).into()
 787        }
 788    );
 789
 790    fake_server.end_progress(format!("{}/0", progress_token));
 791    assert_eq!(
 792        events.next().await.unwrap(),
 793        Event::DiskBasedDiagnosticsFinished {
 794            language_server_id: 0
 795        }
 796    );
 797
 798    let buffer = project
 799        .update(cx, |p, cx| p.open_local_buffer("/dir/a.rs", cx))
 800        .await
 801        .unwrap();
 802
 803    buffer.read_with(cx, |buffer, _| {
 804        let snapshot = buffer.snapshot();
 805        let diagnostics = snapshot
 806            .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
 807            .collect::<Vec<_>>();
 808        assert_eq!(
 809            diagnostics,
 810            &[DiagnosticEntry {
 811                range: Point::new(0, 9)..Point::new(0, 10),
 812                diagnostic: Diagnostic {
 813                    severity: lsp::DiagnosticSeverity::ERROR,
 814                    message: "undefined variable 'A'".to_string(),
 815                    group_id: 0,
 816                    is_primary: true,
 817                    ..Default::default()
 818                }
 819            }]
 820        )
 821    });
 822
 823    // Ensure publishing empty diagnostics twice only results in one update event.
 824    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
 825        uri: Url::from_file_path("/dir/a.rs").unwrap(),
 826        version: None,
 827        diagnostics: Default::default(),
 828    });
 829    assert_eq!(
 830        events.next().await.unwrap(),
 831        Event::DiagnosticsUpdated {
 832            language_server_id: 0,
 833            path: (worktree_id, Path::new("a.rs")).into()
 834        }
 835    );
 836
 837    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
 838        uri: Url::from_file_path("/dir/a.rs").unwrap(),
 839        version: None,
 840        diagnostics: Default::default(),
 841    });
 842    cx.foreground().run_until_parked();
 843    assert_eq!(futures::poll!(events.next()), Poll::Pending);
 844}
 845
 846#[gpui::test]
 847async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppContext) {
 848    cx.foreground().forbid_parking();
 849
 850    let progress_token = "the-progress-token";
 851    let mut language = Language::new(
 852        LanguageConfig {
 853            path_suffixes: vec!["rs".to_string()],
 854            ..Default::default()
 855        },
 856        None,
 857    );
 858    let mut fake_servers = language
 859        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
 860            disk_based_diagnostics_sources: vec!["disk".into()],
 861            disk_based_diagnostics_progress_token: Some(progress_token.into()),
 862            ..Default::default()
 863        }))
 864        .await;
 865
 866    let fs = FakeFs::new(cx.background());
 867    fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
 868
 869    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
 870    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
 871
 872    let buffer = project
 873        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
 874        .await
 875        .unwrap();
 876
 877    // Simulate diagnostics starting to update.
 878    let fake_server = fake_servers.next().await.unwrap();
 879    fake_server.start_progress(progress_token).await;
 880
 881    // Restart the server before the diagnostics finish updating.
 882    project.update(cx, |project, cx| {
 883        project.restart_language_servers_for_buffers([buffer], cx);
 884    });
 885    let mut events = subscribe(&project, cx);
 886
 887    // Simulate the newly started server sending more diagnostics.
 888    let fake_server = fake_servers.next().await.unwrap();
 889    fake_server.start_progress(progress_token).await;
 890    assert_eq!(
 891        events.next().await.unwrap(),
 892        Event::DiskBasedDiagnosticsStarted {
 893            language_server_id: 1
 894        }
 895    );
 896    project.read_with(cx, |project, _| {
 897        assert_eq!(
 898            project
 899                .language_servers_running_disk_based_diagnostics()
 900                .collect::<Vec<_>>(),
 901            [1]
 902        );
 903    });
 904
 905    // All diagnostics are considered done, despite the old server's diagnostic
 906    // task never completing.
 907    fake_server.end_progress(progress_token);
 908    assert_eq!(
 909        events.next().await.unwrap(),
 910        Event::DiskBasedDiagnosticsFinished {
 911            language_server_id: 1
 912        }
 913    );
 914    project.read_with(cx, |project, _| {
 915        assert_eq!(
 916            project
 917                .language_servers_running_disk_based_diagnostics()
 918                .collect::<Vec<_>>(),
 919            [0; 0]
 920        );
 921    });
 922}
 923
 924#[gpui::test]
 925async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::TestAppContext) {
 926    cx.foreground().forbid_parking();
 927
 928    let mut language = Language::new(
 929        LanguageConfig {
 930            path_suffixes: vec!["rs".to_string()],
 931            ..Default::default()
 932        },
 933        None,
 934    );
 935    let mut fake_servers = language
 936        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
 937            name: "the-lsp",
 938            ..Default::default()
 939        }))
 940        .await;
 941
 942    let fs = FakeFs::new(cx.background());
 943    fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
 944
 945    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
 946    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
 947
 948    let buffer = project
 949        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
 950        .await
 951        .unwrap();
 952
 953    // Before restarting the server, report diagnostics with an unknown buffer version.
 954    let fake_server = fake_servers.next().await.unwrap();
 955    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
 956        uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(),
 957        version: Some(10000),
 958        diagnostics: Vec::new(),
 959    });
 960    cx.foreground().run_until_parked();
 961
 962    project.update(cx, |project, cx| {
 963        project.restart_language_servers_for_buffers([buffer.clone()], cx);
 964    });
 965    let mut fake_server = fake_servers.next().await.unwrap();
 966    let notification = fake_server
 967        .receive_notification::<lsp::notification::DidOpenTextDocument>()
 968        .await
 969        .text_document;
 970    assert_eq!(notification.version, 0);
 971}
 972
 973#[gpui::test]
 974async fn test_toggling_enable_language_server(
 975    deterministic: Arc<Deterministic>,
 976    cx: &mut gpui::TestAppContext,
 977) {
 978    deterministic.forbid_parking();
 979
 980    let mut rust = Language::new(
 981        LanguageConfig {
 982            name: Arc::from("Rust"),
 983            path_suffixes: vec!["rs".to_string()],
 984            ..Default::default()
 985        },
 986        None,
 987    );
 988    let mut fake_rust_servers = rust
 989        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
 990            name: "rust-lsp",
 991            ..Default::default()
 992        }))
 993        .await;
 994    let mut js = Language::new(
 995        LanguageConfig {
 996            name: Arc::from("JavaScript"),
 997            path_suffixes: vec!["js".to_string()],
 998            ..Default::default()
 999        },
1000        None,
1001    );
1002    let mut fake_js_servers = js
1003        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1004            name: "js-lsp",
1005            ..Default::default()
1006        }))
1007        .await;
1008
1009    let fs = FakeFs::new(cx.background());
1010    fs.insert_tree("/dir", json!({ "a.rs": "", "b.js": "" }))
1011        .await;
1012
1013    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
1014    project.update(cx, |project, _| {
1015        project.languages.add(Arc::new(rust));
1016        project.languages.add(Arc::new(js));
1017    });
1018
1019    let _rs_buffer = project
1020        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
1021        .await
1022        .unwrap();
1023    let _js_buffer = project
1024        .update(cx, |project, cx| project.open_local_buffer("/dir/b.js", cx))
1025        .await
1026        .unwrap();
1027
1028    let mut fake_rust_server_1 = fake_rust_servers.next().await.unwrap();
1029    assert_eq!(
1030        fake_rust_server_1
1031            .receive_notification::<lsp::notification::DidOpenTextDocument>()
1032            .await
1033            .text_document
1034            .uri
1035            .as_str(),
1036        "file:///dir/a.rs"
1037    );
1038
1039    let mut fake_js_server = fake_js_servers.next().await.unwrap();
1040    assert_eq!(
1041        fake_js_server
1042            .receive_notification::<lsp::notification::DidOpenTextDocument>()
1043            .await
1044            .text_document
1045            .uri
1046            .as_str(),
1047        "file:///dir/b.js"
1048    );
1049
1050    // Disable Rust language server, ensuring only that server gets stopped.
1051    cx.update(|cx| {
1052        cx.update_global(|settings: &mut Settings, _| {
1053            settings.language_overrides.insert(
1054                Arc::from("Rust"),
1055                settings::EditorSettings {
1056                    enable_language_server: Some(false),
1057                    ..Default::default()
1058                },
1059            );
1060        })
1061    });
1062    fake_rust_server_1
1063        .receive_notification::<lsp::notification::Exit>()
1064        .await;
1065
1066    // Enable Rust and disable JavaScript language servers, ensuring that the
1067    // former gets started again and that the latter stops.
1068    cx.update(|cx| {
1069        cx.update_global(|settings: &mut Settings, _| {
1070            settings.language_overrides.insert(
1071                Arc::from("Rust"),
1072                settings::EditorSettings {
1073                    enable_language_server: Some(true),
1074                    ..Default::default()
1075                },
1076            );
1077            settings.language_overrides.insert(
1078                Arc::from("JavaScript"),
1079                settings::EditorSettings {
1080                    enable_language_server: Some(false),
1081                    ..Default::default()
1082                },
1083            );
1084        })
1085    });
1086    let mut fake_rust_server_2 = fake_rust_servers.next().await.unwrap();
1087    assert_eq!(
1088        fake_rust_server_2
1089            .receive_notification::<lsp::notification::DidOpenTextDocument>()
1090            .await
1091            .text_document
1092            .uri
1093            .as_str(),
1094        "file:///dir/a.rs"
1095    );
1096    fake_js_server
1097        .receive_notification::<lsp::notification::Exit>()
1098        .await;
1099}
1100
1101#[gpui::test]
1102async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
1103    cx.foreground().forbid_parking();
1104
1105    let mut language = Language::new(
1106        LanguageConfig {
1107            name: "Rust".into(),
1108            path_suffixes: vec!["rs".to_string()],
1109            ..Default::default()
1110        },
1111        Some(tree_sitter_rust::language()),
1112    );
1113    let mut fake_servers = language
1114        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1115            disk_based_diagnostics_sources: vec!["disk".into()],
1116            ..Default::default()
1117        }))
1118        .await;
1119
1120    let text = "
1121        fn a() { A }
1122        fn b() { BB }
1123        fn c() { CCC }
1124    "
1125    .unindent();
1126
1127    let fs = FakeFs::new(cx.background());
1128    fs.insert_tree("/dir", json!({ "a.rs": text })).await;
1129
1130    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
1131    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
1132
1133    let buffer = project
1134        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
1135        .await
1136        .unwrap();
1137
1138    let mut fake_server = fake_servers.next().await.unwrap();
1139    let open_notification = fake_server
1140        .receive_notification::<lsp::notification::DidOpenTextDocument>()
1141        .await;
1142
1143    // Edit the buffer, moving the content down
1144    buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "\n\n")], None, cx));
1145    let change_notification_1 = fake_server
1146        .receive_notification::<lsp::notification::DidChangeTextDocument>()
1147        .await;
1148    assert!(change_notification_1.text_document.version > open_notification.text_document.version);
1149
1150    // Report some diagnostics for the initial version of the buffer
1151    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
1152        uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(),
1153        version: Some(open_notification.text_document.version),
1154        diagnostics: vec![
1155            lsp::Diagnostic {
1156                range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
1157                severity: Some(DiagnosticSeverity::ERROR),
1158                message: "undefined variable 'A'".to_string(),
1159                source: Some("disk".to_string()),
1160                ..Default::default()
1161            },
1162            lsp::Diagnostic {
1163                range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)),
1164                severity: Some(DiagnosticSeverity::ERROR),
1165                message: "undefined variable 'BB'".to_string(),
1166                source: Some("disk".to_string()),
1167                ..Default::default()
1168            },
1169            lsp::Diagnostic {
1170                range: lsp::Range::new(lsp::Position::new(2, 9), lsp::Position::new(2, 12)),
1171                severity: Some(DiagnosticSeverity::ERROR),
1172                source: Some("disk".to_string()),
1173                message: "undefined variable 'CCC'".to_string(),
1174                ..Default::default()
1175            },
1176        ],
1177    });
1178
1179    // The diagnostics have moved down since they were created.
1180    buffer.next_notification(cx).await;
1181    buffer.read_with(cx, |buffer, _| {
1182        assert_eq!(
1183            buffer
1184                .snapshot()
1185                .diagnostics_in_range::<_, Point>(Point::new(3, 0)..Point::new(5, 0), false)
1186                .collect::<Vec<_>>(),
1187            &[
1188                DiagnosticEntry {
1189                    range: Point::new(3, 9)..Point::new(3, 11),
1190                    diagnostic: Diagnostic {
1191                        severity: DiagnosticSeverity::ERROR,
1192                        message: "undefined variable 'BB'".to_string(),
1193                        is_disk_based: true,
1194                        group_id: 1,
1195                        is_primary: true,
1196                        ..Default::default()
1197                    },
1198                },
1199                DiagnosticEntry {
1200                    range: Point::new(4, 9)..Point::new(4, 12),
1201                    diagnostic: Diagnostic {
1202                        severity: DiagnosticSeverity::ERROR,
1203                        message: "undefined variable 'CCC'".to_string(),
1204                        is_disk_based: true,
1205                        group_id: 2,
1206                        is_primary: true,
1207                        ..Default::default()
1208                    }
1209                }
1210            ]
1211        );
1212        assert_eq!(
1213            chunks_with_diagnostics(buffer, 0..buffer.len()),
1214            [
1215                ("\n\nfn a() { ".to_string(), None),
1216                ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
1217                (" }\nfn b() { ".to_string(), None),
1218                ("BB".to_string(), Some(DiagnosticSeverity::ERROR)),
1219                (" }\nfn c() { ".to_string(), None),
1220                ("CCC".to_string(), Some(DiagnosticSeverity::ERROR)),
1221                (" }\n".to_string(), None),
1222            ]
1223        );
1224        assert_eq!(
1225            chunks_with_diagnostics(buffer, Point::new(3, 10)..Point::new(4, 11)),
1226            [
1227                ("B".to_string(), Some(DiagnosticSeverity::ERROR)),
1228                (" }\nfn c() { ".to_string(), None),
1229                ("CC".to_string(), Some(DiagnosticSeverity::ERROR)),
1230            ]
1231        );
1232    });
1233
1234    // Ensure overlapping diagnostics are highlighted correctly.
1235    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
1236        uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(),
1237        version: Some(open_notification.text_document.version),
1238        diagnostics: vec![
1239            lsp::Diagnostic {
1240                range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
1241                severity: Some(DiagnosticSeverity::ERROR),
1242                message: "undefined variable 'A'".to_string(),
1243                source: Some("disk".to_string()),
1244                ..Default::default()
1245            },
1246            lsp::Diagnostic {
1247                range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 12)),
1248                severity: Some(DiagnosticSeverity::WARNING),
1249                message: "unreachable statement".to_string(),
1250                source: Some("disk".to_string()),
1251                ..Default::default()
1252            },
1253        ],
1254    });
1255
1256    buffer.next_notification(cx).await;
1257    buffer.read_with(cx, |buffer, _| {
1258        assert_eq!(
1259            buffer
1260                .snapshot()
1261                .diagnostics_in_range::<_, Point>(Point::new(2, 0)..Point::new(3, 0), false)
1262                .collect::<Vec<_>>(),
1263            &[
1264                DiagnosticEntry {
1265                    range: Point::new(2, 9)..Point::new(2, 12),
1266                    diagnostic: Diagnostic {
1267                        severity: DiagnosticSeverity::WARNING,
1268                        message: "unreachable statement".to_string(),
1269                        is_disk_based: true,
1270                        group_id: 4,
1271                        is_primary: true,
1272                        ..Default::default()
1273                    }
1274                },
1275                DiagnosticEntry {
1276                    range: Point::new(2, 9)..Point::new(2, 10),
1277                    diagnostic: Diagnostic {
1278                        severity: DiagnosticSeverity::ERROR,
1279                        message: "undefined variable 'A'".to_string(),
1280                        is_disk_based: true,
1281                        group_id: 3,
1282                        is_primary: true,
1283                        ..Default::default()
1284                    },
1285                }
1286            ]
1287        );
1288        assert_eq!(
1289            chunks_with_diagnostics(buffer, Point::new(2, 0)..Point::new(3, 0)),
1290            [
1291                ("fn a() { ".to_string(), None),
1292                ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
1293                (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
1294                ("\n".to_string(), None),
1295            ]
1296        );
1297        assert_eq!(
1298            chunks_with_diagnostics(buffer, Point::new(2, 10)..Point::new(3, 0)),
1299            [
1300                (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
1301                ("\n".to_string(), None),
1302            ]
1303        );
1304    });
1305
1306    // Keep editing the buffer and ensure disk-based diagnostics get translated according to the
1307    // changes since the last save.
1308    buffer.update(cx, |buffer, cx| {
1309        buffer.edit([(Point::new(2, 0)..Point::new(2, 0), "    ")], None, cx);
1310        buffer.edit(
1311            [(Point::new(2, 8)..Point::new(2, 10), "(x: usize)")],
1312            None,
1313            cx,
1314        );
1315        buffer.edit([(Point::new(3, 10)..Point::new(3, 10), "xxx")], None, cx);
1316    });
1317    let change_notification_2 = fake_server
1318        .receive_notification::<lsp::notification::DidChangeTextDocument>()
1319        .await;
1320    assert!(
1321        change_notification_2.text_document.version > change_notification_1.text_document.version
1322    );
1323
1324    // Handle out-of-order diagnostics
1325    fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
1326        uri: lsp::Url::from_file_path("/dir/a.rs").unwrap(),
1327        version: Some(change_notification_2.text_document.version),
1328        diagnostics: vec![
1329            lsp::Diagnostic {
1330                range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)),
1331                severity: Some(DiagnosticSeverity::ERROR),
1332                message: "undefined variable 'BB'".to_string(),
1333                source: Some("disk".to_string()),
1334                ..Default::default()
1335            },
1336            lsp::Diagnostic {
1337                range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
1338                severity: Some(DiagnosticSeverity::WARNING),
1339                message: "undefined variable 'A'".to_string(),
1340                source: Some("disk".to_string()),
1341                ..Default::default()
1342            },
1343        ],
1344    });
1345
1346    buffer.next_notification(cx).await;
1347    buffer.read_with(cx, |buffer, _| {
1348        assert_eq!(
1349            buffer
1350                .snapshot()
1351                .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
1352                .collect::<Vec<_>>(),
1353            &[
1354                DiagnosticEntry {
1355                    range: Point::new(2, 21)..Point::new(2, 22),
1356                    diagnostic: Diagnostic {
1357                        severity: DiagnosticSeverity::WARNING,
1358                        message: "undefined variable 'A'".to_string(),
1359                        is_disk_based: true,
1360                        group_id: 6,
1361                        is_primary: true,
1362                        ..Default::default()
1363                    }
1364                },
1365                DiagnosticEntry {
1366                    range: Point::new(3, 9)..Point::new(3, 14),
1367                    diagnostic: Diagnostic {
1368                        severity: DiagnosticSeverity::ERROR,
1369                        message: "undefined variable 'BB'".to_string(),
1370                        is_disk_based: true,
1371                        group_id: 5,
1372                        is_primary: true,
1373                        ..Default::default()
1374                    },
1375                }
1376            ]
1377        );
1378    });
1379}
1380
1381#[gpui::test]
1382async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
1383    cx.foreground().forbid_parking();
1384
1385    let text = concat!(
1386        "let one = ;\n", //
1387        "let two = \n",
1388        "let three = 3;\n",
1389    );
1390
1391    let fs = FakeFs::new(cx.background());
1392    fs.insert_tree("/dir", json!({ "a.rs": text })).await;
1393
1394    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
1395    let buffer = project
1396        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
1397        .await
1398        .unwrap();
1399
1400    project.update(cx, |project, cx| {
1401        project
1402            .update_buffer_diagnostics(
1403                &buffer,
1404                vec![
1405                    DiagnosticEntry {
1406                        range: Unclipped(PointUtf16::new(0, 10))..Unclipped(PointUtf16::new(0, 10)),
1407                        diagnostic: Diagnostic {
1408                            severity: DiagnosticSeverity::ERROR,
1409                            message: "syntax error 1".to_string(),
1410                            ..Default::default()
1411                        },
1412                    },
1413                    DiagnosticEntry {
1414                        range: Unclipped(PointUtf16::new(1, 10))..Unclipped(PointUtf16::new(1, 10)),
1415                        diagnostic: Diagnostic {
1416                            severity: DiagnosticSeverity::ERROR,
1417                            message: "syntax error 2".to_string(),
1418                            ..Default::default()
1419                        },
1420                    },
1421                ],
1422                None,
1423                cx,
1424            )
1425            .unwrap();
1426    });
1427
1428    // An empty range is extended forward to include the following character.
1429    // At the end of a line, an empty range is extended backward to include
1430    // the preceding character.
1431    buffer.read_with(cx, |buffer, _| {
1432        let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
1433        assert_eq!(
1434            chunks
1435                .iter()
1436                .map(|(s, d)| (s.as_str(), *d))
1437                .collect::<Vec<_>>(),
1438            &[
1439                ("let one = ", None),
1440                (";", Some(DiagnosticSeverity::ERROR)),
1441                ("\nlet two =", None),
1442                (" ", Some(DiagnosticSeverity::ERROR)),
1443                ("\nlet three = 3;\n", None)
1444            ]
1445        );
1446    });
1447}
1448
1449#[gpui::test]
1450async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
1451    cx.foreground().forbid_parking();
1452
1453    let mut language = Language::new(
1454        LanguageConfig {
1455            name: "Rust".into(),
1456            path_suffixes: vec!["rs".to_string()],
1457            ..Default::default()
1458        },
1459        Some(tree_sitter_rust::language()),
1460    );
1461    let mut fake_servers = language.set_fake_lsp_adapter(Default::default()).await;
1462
1463    let text = "
1464        fn a() {
1465            f1();
1466        }
1467        fn b() {
1468            f2();
1469        }
1470        fn c() {
1471            f3();
1472        }
1473    "
1474    .unindent();
1475
1476    let fs = FakeFs::new(cx.background());
1477    fs.insert_tree(
1478        "/dir",
1479        json!({
1480            "a.rs": text.clone(),
1481        }),
1482    )
1483    .await;
1484
1485    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
1486    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
1487    let buffer = project
1488        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
1489        .await
1490        .unwrap();
1491
1492    let mut fake_server = fake_servers.next().await.unwrap();
1493    let lsp_document_version = fake_server
1494        .receive_notification::<lsp::notification::DidOpenTextDocument>()
1495        .await
1496        .text_document
1497        .version;
1498
1499    // Simulate editing the buffer after the language server computes some edits.
1500    buffer.update(cx, |buffer, cx| {
1501        buffer.edit(
1502            [(
1503                Point::new(0, 0)..Point::new(0, 0),
1504                "// above first function\n",
1505            )],
1506            None,
1507            cx,
1508        );
1509        buffer.edit(
1510            [(
1511                Point::new(2, 0)..Point::new(2, 0),
1512                "    // inside first function\n",
1513            )],
1514            None,
1515            cx,
1516        );
1517        buffer.edit(
1518            [(
1519                Point::new(6, 4)..Point::new(6, 4),
1520                "// inside second function ",
1521            )],
1522            None,
1523            cx,
1524        );
1525
1526        assert_eq!(
1527            buffer.text(),
1528            "
1529                // above first function
1530                fn a() {
1531                    // inside first function
1532                    f1();
1533                }
1534                fn b() {
1535                    // inside second function f2();
1536                }
1537                fn c() {
1538                    f3();
1539                }
1540            "
1541            .unindent()
1542        );
1543    });
1544
1545    let edits = project
1546        .update(cx, |project, cx| {
1547            project.edits_from_lsp(
1548                &buffer,
1549                vec![
1550                    // replace body of first function
1551                    lsp::TextEdit {
1552                        range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(3, 0)),
1553                        new_text: "
1554                            fn a() {
1555                                f10();
1556                            }
1557                            "
1558                        .unindent(),
1559                    },
1560                    // edit inside second function
1561                    lsp::TextEdit {
1562                        range: lsp::Range::new(lsp::Position::new(4, 6), lsp::Position::new(4, 6)),
1563                        new_text: "00".into(),
1564                    },
1565                    // edit inside third function via two distinct edits
1566                    lsp::TextEdit {
1567                        range: lsp::Range::new(lsp::Position::new(7, 5), lsp::Position::new(7, 5)),
1568                        new_text: "4000".into(),
1569                    },
1570                    lsp::TextEdit {
1571                        range: lsp::Range::new(lsp::Position::new(7, 5), lsp::Position::new(7, 6)),
1572                        new_text: "".into(),
1573                    },
1574                ],
1575                Some(lsp_document_version),
1576                cx,
1577            )
1578        })
1579        .await
1580        .unwrap();
1581
1582    buffer.update(cx, |buffer, cx| {
1583        for (range, new_text) in edits {
1584            buffer.edit([(range, new_text)], None, cx);
1585        }
1586        assert_eq!(
1587            buffer.text(),
1588            "
1589                // above first function
1590                fn a() {
1591                    // inside first function
1592                    f10();
1593                }
1594                fn b() {
1595                    // inside second function f200();
1596                }
1597                fn c() {
1598                    f4000();
1599                }
1600                "
1601            .unindent()
1602        );
1603    });
1604}
1605
1606#[gpui::test]
1607async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestAppContext) {
1608    cx.foreground().forbid_parking();
1609
1610    let text = "
1611        use a::b;
1612        use a::c;
1613
1614        fn f() {
1615            b();
1616            c();
1617        }
1618    "
1619    .unindent();
1620
1621    let fs = FakeFs::new(cx.background());
1622    fs.insert_tree(
1623        "/dir",
1624        json!({
1625            "a.rs": text.clone(),
1626        }),
1627    )
1628    .await;
1629
1630    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
1631    let buffer = project
1632        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
1633        .await
1634        .unwrap();
1635
1636    // Simulate the language server sending us a small edit in the form of a very large diff.
1637    // Rust-analyzer does this when performing a merge-imports code action.
1638    let edits = project
1639        .update(cx, |project, cx| {
1640            project.edits_from_lsp(
1641                &buffer,
1642                [
1643                    // Replace the first use statement without editing the semicolon.
1644                    lsp::TextEdit {
1645                        range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 8)),
1646                        new_text: "a::{b, c}".into(),
1647                    },
1648                    // Reinsert the remainder of the file between the semicolon and the final
1649                    // newline of the file.
1650                    lsp::TextEdit {
1651                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 9)),
1652                        new_text: "\n\n".into(),
1653                    },
1654                    lsp::TextEdit {
1655                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 9)),
1656                        new_text: "
1657                            fn f() {
1658                                b();
1659                                c();
1660                            }"
1661                        .unindent(),
1662                    },
1663                    // Delete everything after the first newline of the file.
1664                    lsp::TextEdit {
1665                        range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(7, 0)),
1666                        new_text: "".into(),
1667                    },
1668                ],
1669                None,
1670                cx,
1671            )
1672        })
1673        .await
1674        .unwrap();
1675
1676    buffer.update(cx, |buffer, cx| {
1677        let edits = edits
1678            .into_iter()
1679            .map(|(range, text)| {
1680                (
1681                    range.start.to_point(buffer)..range.end.to_point(buffer),
1682                    text,
1683                )
1684            })
1685            .collect::<Vec<_>>();
1686
1687        assert_eq!(
1688            edits,
1689            [
1690                (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()),
1691                (Point::new(1, 0)..Point::new(2, 0), "".into())
1692            ]
1693        );
1694
1695        for (range, new_text) in edits {
1696            buffer.edit([(range, new_text)], None, cx);
1697        }
1698        assert_eq!(
1699            buffer.text(),
1700            "
1701                use a::{b, c};
1702
1703                fn f() {
1704                    b();
1705                    c();
1706                }
1707            "
1708            .unindent()
1709        );
1710    });
1711}
1712
1713#[gpui::test]
1714async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) {
1715    cx.foreground().forbid_parking();
1716
1717    let text = "
1718        use a::b;
1719        use a::c;
1720
1721        fn f() {
1722            b();
1723            c();
1724        }
1725    "
1726    .unindent();
1727
1728    let fs = FakeFs::new(cx.background());
1729    fs.insert_tree(
1730        "/dir",
1731        json!({
1732            "a.rs": text.clone(),
1733        }),
1734    )
1735    .await;
1736
1737    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
1738    let buffer = project
1739        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
1740        .await
1741        .unwrap();
1742
1743    // Simulate the language server sending us edits in a non-ordered fashion,
1744    // with ranges sometimes being inverted or pointing to invalid locations.
1745    let edits = project
1746        .update(cx, |project, cx| {
1747            project.edits_from_lsp(
1748                &buffer,
1749                [
1750                    lsp::TextEdit {
1751                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 9)),
1752                        new_text: "\n\n".into(),
1753                    },
1754                    lsp::TextEdit {
1755                        range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 4)),
1756                        new_text: "a::{b, c}".into(),
1757                    },
1758                    lsp::TextEdit {
1759                        range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(99, 0)),
1760                        new_text: "".into(),
1761                    },
1762                    lsp::TextEdit {
1763                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 9)),
1764                        new_text: "
1765                            fn f() {
1766                                b();
1767                                c();
1768                            }"
1769                        .unindent(),
1770                    },
1771                ],
1772                None,
1773                cx,
1774            )
1775        })
1776        .await
1777        .unwrap();
1778
1779    buffer.update(cx, |buffer, cx| {
1780        let edits = edits
1781            .into_iter()
1782            .map(|(range, text)| {
1783                (
1784                    range.start.to_point(buffer)..range.end.to_point(buffer),
1785                    text,
1786                )
1787            })
1788            .collect::<Vec<_>>();
1789
1790        assert_eq!(
1791            edits,
1792            [
1793                (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()),
1794                (Point::new(1, 0)..Point::new(2, 0), "".into())
1795            ]
1796        );
1797
1798        for (range, new_text) in edits {
1799            buffer.edit([(range, new_text)], None, cx);
1800        }
1801        assert_eq!(
1802            buffer.text(),
1803            "
1804                use a::{b, c};
1805
1806                fn f() {
1807                    b();
1808                    c();
1809                }
1810            "
1811            .unindent()
1812        );
1813    });
1814}
1815
1816fn chunks_with_diagnostics<T: ToOffset + ToPoint>(
1817    buffer: &Buffer,
1818    range: Range<T>,
1819) -> Vec<(String, Option<DiagnosticSeverity>)> {
1820    let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = Vec::new();
1821    for chunk in buffer.snapshot().chunks(range, true) {
1822        if chunks.last().map_or(false, |prev_chunk| {
1823            prev_chunk.1 == chunk.diagnostic_severity
1824        }) {
1825            chunks.last_mut().unwrap().0.push_str(chunk.text);
1826        } else {
1827            chunks.push((chunk.text.to_string(), chunk.diagnostic_severity));
1828        }
1829    }
1830    chunks
1831}
1832
1833#[gpui::test(iterations = 10)]
1834async fn test_definition(cx: &mut gpui::TestAppContext) {
1835    let mut language = Language::new(
1836        LanguageConfig {
1837            name: "Rust".into(),
1838            path_suffixes: vec!["rs".to_string()],
1839            ..Default::default()
1840        },
1841        Some(tree_sitter_rust::language()),
1842    );
1843    let mut fake_servers = language.set_fake_lsp_adapter(Default::default()).await;
1844
1845    let fs = FakeFs::new(cx.background());
1846    fs.insert_tree(
1847        "/dir",
1848        json!({
1849            "a.rs": "const fn a() { A }",
1850            "b.rs": "const y: i32 = crate::a()",
1851        }),
1852    )
1853    .await;
1854
1855    let project = Project::test(fs, ["/dir/b.rs".as_ref()], cx).await;
1856    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
1857
1858    let buffer = project
1859        .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx))
1860        .await
1861        .unwrap();
1862
1863    let fake_server = fake_servers.next().await.unwrap();
1864    fake_server.handle_request::<lsp::request::GotoDefinition, _, _>(|params, _| async move {
1865        let params = params.text_document_position_params;
1866        assert_eq!(
1867            params.text_document.uri.to_file_path().unwrap(),
1868            Path::new("/dir/b.rs"),
1869        );
1870        assert_eq!(params.position, lsp::Position::new(0, 22));
1871
1872        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
1873            lsp::Location::new(
1874                lsp::Url::from_file_path("/dir/a.rs").unwrap(),
1875                lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
1876            ),
1877        )))
1878    });
1879
1880    let mut definitions = project
1881        .update(cx, |project, cx| project.definition(&buffer, 22, cx))
1882        .await
1883        .unwrap();
1884
1885    // Assert no new language server started
1886    cx.foreground().run_until_parked();
1887    assert!(fake_servers.try_next().is_err());
1888
1889    assert_eq!(definitions.len(), 1);
1890    let definition = definitions.pop().unwrap();
1891    cx.update(|cx| {
1892        let target_buffer = definition.target.buffer.read(cx);
1893        assert_eq!(
1894            target_buffer
1895                .file()
1896                .unwrap()
1897                .as_local()
1898                .unwrap()
1899                .abs_path(cx),
1900            Path::new("/dir/a.rs"),
1901        );
1902        assert_eq!(definition.target.range.to_offset(target_buffer), 9..10);
1903        assert_eq!(
1904            list_worktrees(&project, cx),
1905            [("/dir/b.rs".as_ref(), true), ("/dir/a.rs".as_ref(), false)]
1906        );
1907
1908        drop(definition);
1909    });
1910    cx.read(|cx| {
1911        assert_eq!(list_worktrees(&project, cx), [("/dir/b.rs".as_ref(), true)]);
1912    });
1913
1914    fn list_worktrees<'a>(
1915        project: &'a ModelHandle<Project>,
1916        cx: &'a AppContext,
1917    ) -> Vec<(&'a Path, bool)> {
1918        project
1919            .read(cx)
1920            .worktrees(cx)
1921            .map(|worktree| {
1922                let worktree = worktree.read(cx);
1923                (
1924                    worktree.as_local().unwrap().abs_path().as_ref(),
1925                    worktree.is_visible(),
1926                )
1927            })
1928            .collect::<Vec<_>>()
1929    }
1930}
1931
1932#[gpui::test]
1933async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
1934    let mut language = Language::new(
1935        LanguageConfig {
1936            name: "TypeScript".into(),
1937            path_suffixes: vec!["ts".to_string()],
1938            ..Default::default()
1939        },
1940        Some(tree_sitter_typescript::language_typescript()),
1941    );
1942    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
1943
1944    let fs = FakeFs::new(cx.background());
1945    fs.insert_tree(
1946        "/dir",
1947        json!({
1948            "a.ts": "",
1949        }),
1950    )
1951    .await;
1952
1953    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
1954    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
1955    let buffer = project
1956        .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
1957        .await
1958        .unwrap();
1959
1960    let fake_server = fake_language_servers.next().await.unwrap();
1961
1962    let text = "let a = b.fqn";
1963    buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
1964    let completions = project.update(cx, |project, cx| {
1965        project.completions(&buffer, text.len(), cx)
1966    });
1967
1968    fake_server
1969        .handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
1970            Ok(Some(lsp::CompletionResponse::Array(vec![
1971                lsp::CompletionItem {
1972                    label: "fullyQualifiedName?".into(),
1973                    insert_text: Some("fullyQualifiedName".into()),
1974                    ..Default::default()
1975                },
1976            ])))
1977        })
1978        .next()
1979        .await;
1980    let completions = completions.await.unwrap();
1981    let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
1982    assert_eq!(completions.len(), 1);
1983    assert_eq!(completions[0].new_text, "fullyQualifiedName");
1984    assert_eq!(
1985        completions[0].old_range.to_offset(&snapshot),
1986        text.len() - 3..text.len()
1987    );
1988
1989    let text = "let a = \"atoms/cmp\"";
1990    buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
1991    let completions = project.update(cx, |project, cx| {
1992        project.completions(&buffer, text.len() - 1, cx)
1993    });
1994
1995    fake_server
1996        .handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
1997            Ok(Some(lsp::CompletionResponse::Array(vec![
1998                lsp::CompletionItem {
1999                    label: "component".into(),
2000                    ..Default::default()
2001                },
2002            ])))
2003        })
2004        .next()
2005        .await;
2006    let completions = completions.await.unwrap();
2007    let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
2008    assert_eq!(completions.len(), 1);
2009    assert_eq!(completions[0].new_text, "component");
2010    assert_eq!(
2011        completions[0].old_range.to_offset(&snapshot),
2012        text.len() - 4..text.len() - 1
2013    );
2014}
2015
2016#[gpui::test]
2017async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
2018    let mut language = Language::new(
2019        LanguageConfig {
2020            name: "TypeScript".into(),
2021            path_suffixes: vec!["ts".to_string()],
2022            ..Default::default()
2023        },
2024        Some(tree_sitter_typescript::language_typescript()),
2025    );
2026    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2027
2028    let fs = FakeFs::new(cx.background());
2029    fs.insert_tree(
2030        "/dir",
2031        json!({
2032            "a.ts": "",
2033        }),
2034    )
2035    .await;
2036
2037    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
2038    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
2039    let buffer = project
2040        .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
2041        .await
2042        .unwrap();
2043
2044    let fake_server = fake_language_servers.next().await.unwrap();
2045
2046    let text = "let a = b.fqn";
2047    buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
2048    let completions = project.update(cx, |project, cx| {
2049        project.completions(&buffer, text.len(), cx)
2050    });
2051
2052    fake_server
2053        .handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
2054            Ok(Some(lsp::CompletionResponse::Array(vec![
2055                lsp::CompletionItem {
2056                    label: "fullyQualifiedName?".into(),
2057                    insert_text: Some("fully\rQualified\r\nName".into()),
2058                    ..Default::default()
2059                },
2060            ])))
2061        })
2062        .next()
2063        .await;
2064    let completions = completions.await.unwrap();
2065    assert_eq!(completions.len(), 1);
2066    assert_eq!(completions[0].new_text, "fully\nQualified\nName");
2067}
2068
2069#[gpui::test(iterations = 10)]
2070async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
2071    let mut language = Language::new(
2072        LanguageConfig {
2073            name: "TypeScript".into(),
2074            path_suffixes: vec!["ts".to_string()],
2075            ..Default::default()
2076        },
2077        None,
2078    );
2079    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2080
2081    let fs = FakeFs::new(cx.background());
2082    fs.insert_tree(
2083        "/dir",
2084        json!({
2085            "a.ts": "a",
2086        }),
2087    )
2088    .await;
2089
2090    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
2091    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
2092    let buffer = project
2093        .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
2094        .await
2095        .unwrap();
2096
2097    let fake_server = fake_language_servers.next().await.unwrap();
2098
2099    // Language server returns code actions that contain commands, and not edits.
2100    let actions = project.update(cx, |project, cx| project.code_actions(&buffer, 0..0, cx));
2101    fake_server
2102        .handle_request::<lsp::request::CodeActionRequest, _, _>(|_, _| async move {
2103            Ok(Some(vec![
2104                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
2105                    title: "The code action".into(),
2106                    command: Some(lsp::Command {
2107                        title: "The command".into(),
2108                        command: "_the/command".into(),
2109                        arguments: Some(vec![json!("the-argument")]),
2110                    }),
2111                    ..Default::default()
2112                }),
2113                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
2114                    title: "two".into(),
2115                    ..Default::default()
2116                }),
2117            ]))
2118        })
2119        .next()
2120        .await;
2121
2122    let action = actions.await.unwrap()[0].clone();
2123    let apply = project.update(cx, |project, cx| {
2124        project.apply_code_action(buffer.clone(), action, true, cx)
2125    });
2126
2127    // Resolving the code action does not populate its edits. In absence of
2128    // edits, we must execute the given command.
2129    fake_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
2130        |action, _| async move { Ok(action) },
2131    );
2132
2133    // While executing the command, the language server sends the editor
2134    // a `workspaceEdit` request.
2135    fake_server
2136        .handle_request::<lsp::request::ExecuteCommand, _, _>({
2137            let fake = fake_server.clone();
2138            move |params, _| {
2139                assert_eq!(params.command, "_the/command");
2140                let fake = fake.clone();
2141                async move {
2142                    fake.server
2143                        .request::<lsp::request::ApplyWorkspaceEdit>(
2144                            lsp::ApplyWorkspaceEditParams {
2145                                label: None,
2146                                edit: lsp::WorkspaceEdit {
2147                                    changes: Some(
2148                                        [(
2149                                            lsp::Url::from_file_path("/dir/a.ts").unwrap(),
2150                                            vec![lsp::TextEdit {
2151                                                range: lsp::Range::new(
2152                                                    lsp::Position::new(0, 0),
2153                                                    lsp::Position::new(0, 0),
2154                                                ),
2155                                                new_text: "X".into(),
2156                                            }],
2157                                        )]
2158                                        .into_iter()
2159                                        .collect(),
2160                                    ),
2161                                    ..Default::default()
2162                                },
2163                            },
2164                        )
2165                        .await
2166                        .unwrap();
2167                    Ok(Some(json!(null)))
2168                }
2169            }
2170        })
2171        .next()
2172        .await;
2173
2174    // Applying the code action returns a project transaction containing the edits
2175    // sent by the language server in its `workspaceEdit` request.
2176    let transaction = apply.await.unwrap();
2177    assert!(transaction.0.contains_key(&buffer));
2178    buffer.update(cx, |buffer, cx| {
2179        assert_eq!(buffer.text(), "Xa");
2180        buffer.undo(cx);
2181        assert_eq!(buffer.text(), "a");
2182    });
2183}
2184
2185#[gpui::test]
2186async fn test_save_file(cx: &mut gpui::TestAppContext) {
2187    let fs = FakeFs::new(cx.background());
2188    fs.insert_tree(
2189        "/dir",
2190        json!({
2191            "file1": "the old contents",
2192        }),
2193    )
2194    .await;
2195
2196    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
2197    let buffer = project
2198        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
2199        .await
2200        .unwrap();
2201    buffer.update(cx, |buffer, cx| {
2202        assert_eq!(buffer.text(), "the old contents");
2203        buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx);
2204    });
2205
2206    project
2207        .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
2208        .await
2209        .unwrap();
2210
2211    let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
2212    assert_eq!(new_text, buffer.read_with(cx, |buffer, _| buffer.text()));
2213}
2214
2215#[gpui::test]
2216async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) {
2217    let fs = FakeFs::new(cx.background());
2218    fs.insert_tree(
2219        "/dir",
2220        json!({
2221            "file1": "the old contents",
2222        }),
2223    )
2224    .await;
2225
2226    let project = Project::test(fs.clone(), ["/dir/file1".as_ref()], cx).await;
2227    let buffer = project
2228        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
2229        .await
2230        .unwrap();
2231    buffer.update(cx, |buffer, cx| {
2232        buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx);
2233    });
2234
2235    project
2236        .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
2237        .await
2238        .unwrap();
2239
2240    let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
2241    assert_eq!(new_text, buffer.read_with(cx, |buffer, _| buffer.text()));
2242}
2243
2244#[gpui::test]
2245async fn test_save_as(cx: &mut gpui::TestAppContext) {
2246    let fs = FakeFs::new(cx.background());
2247    fs.insert_tree("/dir", json!({})).await;
2248
2249    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
2250
2251    let languages = project.read_with(cx, |project, _| project.languages().clone());
2252    languages.register(
2253        "/some/path",
2254        LanguageConfig {
2255            name: "Rust".into(),
2256            path_suffixes: vec!["rs".into()],
2257            ..Default::default()
2258        },
2259        tree_sitter_rust::language(),
2260        None,
2261        |_| Default::default(),
2262    );
2263
2264    let buffer = project.update(cx, |project, cx| {
2265        project.create_buffer("", None, cx).unwrap()
2266    });
2267    buffer.update(cx, |buffer, cx| {
2268        buffer.edit([(0..0, "abc")], None, cx);
2269        assert!(buffer.is_dirty());
2270        assert!(!buffer.has_conflict());
2271        assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text");
2272    });
2273    project
2274        .update(cx, |project, cx| {
2275            project.save_buffer_as(buffer.clone(), "/dir/file1.rs".into(), cx)
2276        })
2277        .await
2278        .unwrap();
2279    assert_eq!(fs.load(Path::new("/dir/file1.rs")).await.unwrap(), "abc");
2280
2281    cx.foreground().run_until_parked();
2282    buffer.read_with(cx, |buffer, cx| {
2283        assert_eq!(
2284            buffer.file().unwrap().full_path(cx),
2285            Path::new("dir/file1.rs")
2286        );
2287        assert!(!buffer.is_dirty());
2288        assert!(!buffer.has_conflict());
2289        assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust");
2290    });
2291
2292    let opened_buffer = project
2293        .update(cx, |project, cx| {
2294            project.open_local_buffer("/dir/file1.rs", cx)
2295        })
2296        .await
2297        .unwrap();
2298    assert_eq!(opened_buffer, buffer);
2299}
2300
2301#[gpui::test(retries = 5)]
2302async fn test_rescan_and_remote_updates(
2303    deterministic: Arc<Deterministic>,
2304    cx: &mut gpui::TestAppContext,
2305) {
2306    let dir = temp_tree(json!({
2307        "a": {
2308            "file1": "",
2309            "file2": "",
2310            "file3": "",
2311        },
2312        "b": {
2313            "c": {
2314                "file4": "",
2315                "file5": "",
2316            }
2317        }
2318    }));
2319
2320    let project = Project::test(Arc::new(RealFs), [dir.path()], cx).await;
2321    let rpc = project.read_with(cx, |p, _| p.client.clone());
2322
2323    let buffer_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| {
2324        let buffer = project.update(cx, |p, cx| p.open_local_buffer(dir.path().join(path), cx));
2325        async move { buffer.await.unwrap() }
2326    };
2327    let id_for_path = |path: &'static str, cx: &gpui::TestAppContext| {
2328        project.read_with(cx, |project, cx| {
2329            let tree = project.worktrees(cx).next().unwrap();
2330            tree.read(cx)
2331                .entry_for_path(path)
2332                .unwrap_or_else(|| panic!("no entry for path {}", path))
2333                .id
2334        })
2335    };
2336
2337    let buffer2 = buffer_for_path("a/file2", cx).await;
2338    let buffer3 = buffer_for_path("a/file3", cx).await;
2339    let buffer4 = buffer_for_path("b/c/file4", cx).await;
2340    let buffer5 = buffer_for_path("b/c/file5", cx).await;
2341
2342    let file2_id = id_for_path("a/file2", cx);
2343    let file3_id = id_for_path("a/file3", cx);
2344    let file4_id = id_for_path("b/c/file4", cx);
2345
2346    // Create a remote copy of this worktree.
2347    let tree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap());
2348    let initial_snapshot = tree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
2349    let remote = cx.update(|cx| {
2350        Worktree::remote(
2351            1,
2352            1,
2353            proto::WorktreeMetadata {
2354                id: initial_snapshot.id().to_proto(),
2355                root_name: initial_snapshot.root_name().into(),
2356                abs_path: initial_snapshot
2357                    .abs_path()
2358                    .as_os_str()
2359                    .to_string_lossy()
2360                    .into(),
2361                visible: true,
2362            },
2363            rpc.clone(),
2364            cx,
2365        )
2366    });
2367    remote.update(cx, |remote, _| {
2368        let update = initial_snapshot.build_initial_update(1);
2369        remote.as_remote_mut().unwrap().update_from_remote(update);
2370    });
2371    deterministic.run_until_parked();
2372
2373    cx.read(|cx| {
2374        assert!(!buffer2.read(cx).is_dirty());
2375        assert!(!buffer3.read(cx).is_dirty());
2376        assert!(!buffer4.read(cx).is_dirty());
2377        assert!(!buffer5.read(cx).is_dirty());
2378    });
2379
2380    // Rename and delete files and directories.
2381    tree.flush_fs_events(cx).await;
2382    std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
2383    std::fs::remove_file(dir.path().join("b/c/file5")).unwrap();
2384    std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
2385    std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap();
2386    tree.flush_fs_events(cx).await;
2387
2388    let expected_paths = vec![
2389        "a",
2390        "a/file1",
2391        "a/file2.new",
2392        "b",
2393        "d",
2394        "d/file3",
2395        "d/file4",
2396    ];
2397
2398    cx.read(|app| {
2399        assert_eq!(
2400            tree.read(app)
2401                .paths()
2402                .map(|p| p.to_str().unwrap())
2403                .collect::<Vec<_>>(),
2404            expected_paths
2405        );
2406
2407        assert_eq!(id_for_path("a/file2.new", cx), file2_id);
2408        assert_eq!(id_for_path("d/file3", cx), file3_id);
2409        assert_eq!(id_for_path("d/file4", cx), file4_id);
2410
2411        assert_eq!(
2412            buffer2.read(app).file().unwrap().path().as_ref(),
2413            Path::new("a/file2.new")
2414        );
2415        assert_eq!(
2416            buffer3.read(app).file().unwrap().path().as_ref(),
2417            Path::new("d/file3")
2418        );
2419        assert_eq!(
2420            buffer4.read(app).file().unwrap().path().as_ref(),
2421            Path::new("d/file4")
2422        );
2423        assert_eq!(
2424            buffer5.read(app).file().unwrap().path().as_ref(),
2425            Path::new("b/c/file5")
2426        );
2427
2428        assert!(!buffer2.read(app).file().unwrap().is_deleted());
2429        assert!(!buffer3.read(app).file().unwrap().is_deleted());
2430        assert!(!buffer4.read(app).file().unwrap().is_deleted());
2431        assert!(buffer5.read(app).file().unwrap().is_deleted());
2432    });
2433
2434    // Update the remote worktree. Check that it becomes consistent with the
2435    // local worktree.
2436    remote.update(cx, |remote, cx| {
2437        let update = tree.read(cx).as_local().unwrap().snapshot().build_update(
2438            &initial_snapshot,
2439            1,
2440            1,
2441            true,
2442        );
2443        remote.as_remote_mut().unwrap().update_from_remote(update);
2444    });
2445    deterministic.run_until_parked();
2446    remote.read_with(cx, |remote, _| {
2447        assert_eq!(
2448            remote
2449                .paths()
2450                .map(|p| p.to_str().unwrap())
2451                .collect::<Vec<_>>(),
2452            expected_paths
2453        );
2454    });
2455}
2456
2457#[gpui::test(iterations = 10)]
2458async fn test_buffer_identity_across_renames(
2459    deterministic: Arc<Deterministic>,
2460    cx: &mut gpui::TestAppContext,
2461) {
2462    let fs = FakeFs::new(cx.background());
2463    fs.insert_tree(
2464        "/dir",
2465        json!({
2466            "a": {
2467                "file1": "",
2468            }
2469        }),
2470    )
2471    .await;
2472
2473    let project = Project::test(fs, [Path::new("/dir")], cx).await;
2474    let tree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap());
2475    let tree_id = tree.read_with(cx, |tree, _| tree.id());
2476
2477    let id_for_path = |path: &'static str, cx: &gpui::TestAppContext| {
2478        project.read_with(cx, |project, cx| {
2479            let tree = project.worktrees(cx).next().unwrap();
2480            tree.read(cx)
2481                .entry_for_path(path)
2482                .unwrap_or_else(|| panic!("no entry for path {}", path))
2483                .id
2484        })
2485    };
2486
2487    let dir_id = id_for_path("a", cx);
2488    let file_id = id_for_path("a/file1", cx);
2489    let buffer = project
2490        .update(cx, |p, cx| p.open_buffer((tree_id, "a/file1"), cx))
2491        .await
2492        .unwrap();
2493    buffer.read_with(cx, |buffer, _| assert!(!buffer.is_dirty()));
2494
2495    project
2496        .update(cx, |project, cx| {
2497            project.rename_entry(dir_id, Path::new("b"), cx)
2498        })
2499        .unwrap()
2500        .await
2501        .unwrap();
2502    deterministic.run_until_parked();
2503    assert_eq!(id_for_path("b", cx), dir_id);
2504    assert_eq!(id_for_path("b/file1", cx), file_id);
2505    buffer.read_with(cx, |buffer, _| assert!(!buffer.is_dirty()));
2506}
2507
2508#[gpui::test]
2509async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) {
2510    let fs = FakeFs::new(cx.background());
2511    fs.insert_tree(
2512        "/dir",
2513        json!({
2514            "a.txt": "a-contents",
2515            "b.txt": "b-contents",
2516        }),
2517    )
2518    .await;
2519
2520    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
2521
2522    // Spawn multiple tasks to open paths, repeating some paths.
2523    let (buffer_a_1, buffer_b, buffer_a_2) = project.update(cx, |p, cx| {
2524        (
2525            p.open_local_buffer("/dir/a.txt", cx),
2526            p.open_local_buffer("/dir/b.txt", cx),
2527            p.open_local_buffer("/dir/a.txt", cx),
2528        )
2529    });
2530
2531    let buffer_a_1 = buffer_a_1.await.unwrap();
2532    let buffer_a_2 = buffer_a_2.await.unwrap();
2533    let buffer_b = buffer_b.await.unwrap();
2534    assert_eq!(buffer_a_1.read_with(cx, |b, _| b.text()), "a-contents");
2535    assert_eq!(buffer_b.read_with(cx, |b, _| b.text()), "b-contents");
2536
2537    // There is only one buffer per path.
2538    let buffer_a_id = buffer_a_1.id();
2539    assert_eq!(buffer_a_2.id(), buffer_a_id);
2540
2541    // Open the same path again while it is still open.
2542    drop(buffer_a_1);
2543    let buffer_a_3 = project
2544        .update(cx, |p, cx| p.open_local_buffer("/dir/a.txt", cx))
2545        .await
2546        .unwrap();
2547
2548    // There's still only one buffer per path.
2549    assert_eq!(buffer_a_3.id(), buffer_a_id);
2550}
2551
2552#[gpui::test]
2553async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
2554    let fs = FakeFs::new(cx.background());
2555    fs.insert_tree(
2556        "/dir",
2557        json!({
2558            "file1": "abc",
2559            "file2": "def",
2560            "file3": "ghi",
2561        }),
2562    )
2563    .await;
2564
2565    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
2566
2567    let buffer1 = project
2568        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
2569        .await
2570        .unwrap();
2571    let events = Rc::new(RefCell::new(Vec::new()));
2572
2573    // initially, the buffer isn't dirty.
2574    buffer1.update(cx, |buffer, cx| {
2575        cx.subscribe(&buffer1, {
2576            let events = events.clone();
2577            move |_, _, event, _| match event {
2578                BufferEvent::Operation(_) => {}
2579                _ => events.borrow_mut().push(event.clone()),
2580            }
2581        })
2582        .detach();
2583
2584        assert!(!buffer.is_dirty());
2585        assert!(events.borrow().is_empty());
2586
2587        buffer.edit([(1..2, "")], None, cx);
2588    });
2589
2590    // after the first edit, the buffer is dirty, and emits a dirtied event.
2591    buffer1.update(cx, |buffer, cx| {
2592        assert!(buffer.text() == "ac");
2593        assert!(buffer.is_dirty());
2594        assert_eq!(
2595            *events.borrow(),
2596            &[language::Event::Edited, language::Event::DirtyChanged]
2597        );
2598        events.borrow_mut().clear();
2599        buffer.did_save(
2600            buffer.version(),
2601            buffer.as_rope().fingerprint(),
2602            buffer.file().unwrap().mtime(),
2603            cx,
2604        );
2605    });
2606
2607    // after saving, the buffer is not dirty, and emits a saved event.
2608    buffer1.update(cx, |buffer, cx| {
2609        assert!(!buffer.is_dirty());
2610        assert_eq!(*events.borrow(), &[language::Event::Saved]);
2611        events.borrow_mut().clear();
2612
2613        buffer.edit([(1..1, "B")], None, cx);
2614        buffer.edit([(2..2, "D")], None, cx);
2615    });
2616
2617    // after editing again, the buffer is dirty, and emits another dirty event.
2618    buffer1.update(cx, |buffer, cx| {
2619        assert!(buffer.text() == "aBDc");
2620        assert!(buffer.is_dirty());
2621        assert_eq!(
2622            *events.borrow(),
2623            &[
2624                language::Event::Edited,
2625                language::Event::DirtyChanged,
2626                language::Event::Edited,
2627            ],
2628        );
2629        events.borrow_mut().clear();
2630
2631        // After restoring the buffer to its previously-saved state,
2632        // the buffer is not considered dirty anymore.
2633        buffer.edit([(1..3, "")], None, cx);
2634        assert!(buffer.text() == "ac");
2635        assert!(!buffer.is_dirty());
2636    });
2637
2638    assert_eq!(
2639        *events.borrow(),
2640        &[language::Event::Edited, language::Event::DirtyChanged]
2641    );
2642
2643    // When a file is deleted, the buffer is considered dirty.
2644    let events = Rc::new(RefCell::new(Vec::new()));
2645    let buffer2 = project
2646        .update(cx, |p, cx| p.open_local_buffer("/dir/file2", cx))
2647        .await
2648        .unwrap();
2649    buffer2.update(cx, |_, cx| {
2650        cx.subscribe(&buffer2, {
2651            let events = events.clone();
2652            move |_, _, event, _| events.borrow_mut().push(event.clone())
2653        })
2654        .detach();
2655    });
2656
2657    fs.remove_file("/dir/file2".as_ref(), Default::default())
2658        .await
2659        .unwrap();
2660    cx.foreground().run_until_parked();
2661    buffer2.read_with(cx, |buffer, _| assert!(buffer.is_dirty()));
2662    assert_eq!(
2663        *events.borrow(),
2664        &[
2665            language::Event::DirtyChanged,
2666            language::Event::FileHandleChanged
2667        ]
2668    );
2669
2670    // When a file is already dirty when deleted, we don't emit a Dirtied event.
2671    let events = Rc::new(RefCell::new(Vec::new()));
2672    let buffer3 = project
2673        .update(cx, |p, cx| p.open_local_buffer("/dir/file3", cx))
2674        .await
2675        .unwrap();
2676    buffer3.update(cx, |_, cx| {
2677        cx.subscribe(&buffer3, {
2678            let events = events.clone();
2679            move |_, _, event, _| events.borrow_mut().push(event.clone())
2680        })
2681        .detach();
2682    });
2683
2684    buffer3.update(cx, |buffer, cx| {
2685        buffer.edit([(0..0, "x")], None, cx);
2686    });
2687    events.borrow_mut().clear();
2688    fs.remove_file("/dir/file3".as_ref(), Default::default())
2689        .await
2690        .unwrap();
2691    cx.foreground().run_until_parked();
2692    assert_eq!(*events.borrow(), &[language::Event::FileHandleChanged]);
2693    cx.read(|cx| assert!(buffer3.read(cx).is_dirty()));
2694}
2695
2696#[gpui::test]
2697async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
2698    let initial_contents = "aaa\nbbbbb\nc\n";
2699    let fs = FakeFs::new(cx.background());
2700    fs.insert_tree(
2701        "/dir",
2702        json!({
2703            "the-file": initial_contents,
2704        }),
2705    )
2706    .await;
2707    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
2708    let buffer = project
2709        .update(cx, |p, cx| p.open_local_buffer("/dir/the-file", cx))
2710        .await
2711        .unwrap();
2712
2713    let anchors = (0..3)
2714        .map(|row| buffer.read_with(cx, |b, _| b.anchor_before(Point::new(row, 1))))
2715        .collect::<Vec<_>>();
2716
2717    // Change the file on disk, adding two new lines of text, and removing
2718    // one line.
2719    buffer.read_with(cx, |buffer, _| {
2720        assert!(!buffer.is_dirty());
2721        assert!(!buffer.has_conflict());
2722    });
2723    let new_contents = "AAAA\naaa\nBB\nbbbbb\n";
2724    fs.save(
2725        "/dir/the-file".as_ref(),
2726        &new_contents.into(),
2727        LineEnding::Unix,
2728    )
2729    .await
2730    .unwrap();
2731
2732    // Because the buffer was not modified, it is reloaded from disk. Its
2733    // contents are edited according to the diff between the old and new
2734    // file contents.
2735    cx.foreground().run_until_parked();
2736    buffer.update(cx, |buffer, _| {
2737        assert_eq!(buffer.text(), new_contents);
2738        assert!(!buffer.is_dirty());
2739        assert!(!buffer.has_conflict());
2740
2741        let anchor_positions = anchors
2742            .iter()
2743            .map(|anchor| anchor.to_point(&*buffer))
2744            .collect::<Vec<_>>();
2745        assert_eq!(
2746            anchor_positions,
2747            [Point::new(1, 1), Point::new(3, 1), Point::new(3, 5)]
2748        );
2749    });
2750
2751    // Modify the buffer
2752    buffer.update(cx, |buffer, cx| {
2753        buffer.edit([(0..0, " ")], None, cx);
2754        assert!(buffer.is_dirty());
2755        assert!(!buffer.has_conflict());
2756    });
2757
2758    // Change the file on disk again, adding blank lines to the beginning.
2759    fs.save(
2760        "/dir/the-file".as_ref(),
2761        &"\n\n\nAAAA\naaa\nBB\nbbbbb\n".into(),
2762        LineEnding::Unix,
2763    )
2764    .await
2765    .unwrap();
2766
2767    // Because the buffer is modified, it doesn't reload from disk, but is
2768    // marked as having a conflict.
2769    cx.foreground().run_until_parked();
2770    buffer.read_with(cx, |buffer, _| {
2771        assert!(buffer.has_conflict());
2772    });
2773}
2774
2775#[gpui::test]
2776async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) {
2777    let fs = FakeFs::new(cx.background());
2778    fs.insert_tree(
2779        "/dir",
2780        json!({
2781            "file1": "a\nb\nc\n",
2782            "file2": "one\r\ntwo\r\nthree\r\n",
2783        }),
2784    )
2785    .await;
2786
2787    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
2788    let buffer1 = project
2789        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
2790        .await
2791        .unwrap();
2792    let buffer2 = project
2793        .update(cx, |p, cx| p.open_local_buffer("/dir/file2", cx))
2794        .await
2795        .unwrap();
2796
2797    buffer1.read_with(cx, |buffer, _| {
2798        assert_eq!(buffer.text(), "a\nb\nc\n");
2799        assert_eq!(buffer.line_ending(), LineEnding::Unix);
2800    });
2801    buffer2.read_with(cx, |buffer, _| {
2802        assert_eq!(buffer.text(), "one\ntwo\nthree\n");
2803        assert_eq!(buffer.line_ending(), LineEnding::Windows);
2804    });
2805
2806    // Change a file's line endings on disk from unix to windows. The buffer's
2807    // state updates correctly.
2808    fs.save(
2809        "/dir/file1".as_ref(),
2810        &"aaa\nb\nc\n".into(),
2811        LineEnding::Windows,
2812    )
2813    .await
2814    .unwrap();
2815    cx.foreground().run_until_parked();
2816    buffer1.read_with(cx, |buffer, _| {
2817        assert_eq!(buffer.text(), "aaa\nb\nc\n");
2818        assert_eq!(buffer.line_ending(), LineEnding::Windows);
2819    });
2820
2821    // Save a file with windows line endings. The file is written correctly.
2822    buffer2.update(cx, |buffer, cx| {
2823        buffer.set_text("one\ntwo\nthree\nfour\n", cx);
2824    });
2825    project
2826        .update(cx, |project, cx| project.save_buffer(buffer2, cx))
2827        .await
2828        .unwrap();
2829    assert_eq!(
2830        fs.load("/dir/file2".as_ref()).await.unwrap(),
2831        "one\r\ntwo\r\nthree\r\nfour\r\n",
2832    );
2833}
2834
2835#[gpui::test]
2836async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
2837    cx.foreground().forbid_parking();
2838
2839    let fs = FakeFs::new(cx.background());
2840    fs.insert_tree(
2841        "/the-dir",
2842        json!({
2843            "a.rs": "
2844                fn foo(mut v: Vec<usize>) {
2845                    for x in &v {
2846                        v.push(1);
2847                    }
2848                }
2849            "
2850            .unindent(),
2851        }),
2852    )
2853    .await;
2854
2855    let project = Project::test(fs.clone(), ["/the-dir".as_ref()], cx).await;
2856    let buffer = project
2857        .update(cx, |p, cx| p.open_local_buffer("/the-dir/a.rs", cx))
2858        .await
2859        .unwrap();
2860
2861    let buffer_uri = Url::from_file_path("/the-dir/a.rs").unwrap();
2862    let message = lsp::PublishDiagnosticsParams {
2863        uri: buffer_uri.clone(),
2864        diagnostics: vec![
2865            lsp::Diagnostic {
2866                range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
2867                severity: Some(DiagnosticSeverity::WARNING),
2868                message: "error 1".to_string(),
2869                related_information: Some(vec![lsp::DiagnosticRelatedInformation {
2870                    location: lsp::Location {
2871                        uri: buffer_uri.clone(),
2872                        range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
2873                    },
2874                    message: "error 1 hint 1".to_string(),
2875                }]),
2876                ..Default::default()
2877            },
2878            lsp::Diagnostic {
2879                range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
2880                severity: Some(DiagnosticSeverity::HINT),
2881                message: "error 1 hint 1".to_string(),
2882                related_information: Some(vec![lsp::DiagnosticRelatedInformation {
2883                    location: lsp::Location {
2884                        uri: buffer_uri.clone(),
2885                        range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
2886                    },
2887                    message: "original diagnostic".to_string(),
2888                }]),
2889                ..Default::default()
2890            },
2891            lsp::Diagnostic {
2892                range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
2893                severity: Some(DiagnosticSeverity::ERROR),
2894                message: "error 2".to_string(),
2895                related_information: Some(vec![
2896                    lsp::DiagnosticRelatedInformation {
2897                        location: lsp::Location {
2898                            uri: buffer_uri.clone(),
2899                            range: lsp::Range::new(
2900                                lsp::Position::new(1, 13),
2901                                lsp::Position::new(1, 15),
2902                            ),
2903                        },
2904                        message: "error 2 hint 1".to_string(),
2905                    },
2906                    lsp::DiagnosticRelatedInformation {
2907                        location: lsp::Location {
2908                            uri: buffer_uri.clone(),
2909                            range: lsp::Range::new(
2910                                lsp::Position::new(1, 13),
2911                                lsp::Position::new(1, 15),
2912                            ),
2913                        },
2914                        message: "error 2 hint 2".to_string(),
2915                    },
2916                ]),
2917                ..Default::default()
2918            },
2919            lsp::Diagnostic {
2920                range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
2921                severity: Some(DiagnosticSeverity::HINT),
2922                message: "error 2 hint 1".to_string(),
2923                related_information: Some(vec![lsp::DiagnosticRelatedInformation {
2924                    location: lsp::Location {
2925                        uri: buffer_uri.clone(),
2926                        range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
2927                    },
2928                    message: "original diagnostic".to_string(),
2929                }]),
2930                ..Default::default()
2931            },
2932            lsp::Diagnostic {
2933                range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
2934                severity: Some(DiagnosticSeverity::HINT),
2935                message: "error 2 hint 2".to_string(),
2936                related_information: Some(vec![lsp::DiagnosticRelatedInformation {
2937                    location: lsp::Location {
2938                        uri: buffer_uri,
2939                        range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
2940                    },
2941                    message: "original diagnostic".to_string(),
2942                }]),
2943                ..Default::default()
2944            },
2945        ],
2946        version: None,
2947    };
2948
2949    project
2950        .update(cx, |p, cx| p.update_diagnostics(0, message, &[], cx))
2951        .unwrap();
2952    let buffer = buffer.read_with(cx, |buffer, _| buffer.snapshot());
2953
2954    assert_eq!(
2955        buffer
2956            .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
2957            .collect::<Vec<_>>(),
2958        &[
2959            DiagnosticEntry {
2960                range: Point::new(1, 8)..Point::new(1, 9),
2961                diagnostic: Diagnostic {
2962                    severity: DiagnosticSeverity::WARNING,
2963                    message: "error 1".to_string(),
2964                    group_id: 1,
2965                    is_primary: true,
2966                    ..Default::default()
2967                }
2968            },
2969            DiagnosticEntry {
2970                range: Point::new(1, 8)..Point::new(1, 9),
2971                diagnostic: Diagnostic {
2972                    severity: DiagnosticSeverity::HINT,
2973                    message: "error 1 hint 1".to_string(),
2974                    group_id: 1,
2975                    is_primary: false,
2976                    ..Default::default()
2977                }
2978            },
2979            DiagnosticEntry {
2980                range: Point::new(1, 13)..Point::new(1, 15),
2981                diagnostic: Diagnostic {
2982                    severity: DiagnosticSeverity::HINT,
2983                    message: "error 2 hint 1".to_string(),
2984                    group_id: 0,
2985                    is_primary: false,
2986                    ..Default::default()
2987                }
2988            },
2989            DiagnosticEntry {
2990                range: Point::new(1, 13)..Point::new(1, 15),
2991                diagnostic: Diagnostic {
2992                    severity: DiagnosticSeverity::HINT,
2993                    message: "error 2 hint 2".to_string(),
2994                    group_id: 0,
2995                    is_primary: false,
2996                    ..Default::default()
2997                }
2998            },
2999            DiagnosticEntry {
3000                range: Point::new(2, 8)..Point::new(2, 17),
3001                diagnostic: Diagnostic {
3002                    severity: DiagnosticSeverity::ERROR,
3003                    message: "error 2".to_string(),
3004                    group_id: 0,
3005                    is_primary: true,
3006                    ..Default::default()
3007                }
3008            }
3009        ]
3010    );
3011
3012    assert_eq!(
3013        buffer.diagnostic_group::<Point>(0).collect::<Vec<_>>(),
3014        &[
3015            DiagnosticEntry {
3016                range: Point::new(1, 13)..Point::new(1, 15),
3017                diagnostic: Diagnostic {
3018                    severity: DiagnosticSeverity::HINT,
3019                    message: "error 2 hint 1".to_string(),
3020                    group_id: 0,
3021                    is_primary: false,
3022                    ..Default::default()
3023                }
3024            },
3025            DiagnosticEntry {
3026                range: Point::new(1, 13)..Point::new(1, 15),
3027                diagnostic: Diagnostic {
3028                    severity: DiagnosticSeverity::HINT,
3029                    message: "error 2 hint 2".to_string(),
3030                    group_id: 0,
3031                    is_primary: false,
3032                    ..Default::default()
3033                }
3034            },
3035            DiagnosticEntry {
3036                range: Point::new(2, 8)..Point::new(2, 17),
3037                diagnostic: Diagnostic {
3038                    severity: DiagnosticSeverity::ERROR,
3039                    message: "error 2".to_string(),
3040                    group_id: 0,
3041                    is_primary: true,
3042                    ..Default::default()
3043                }
3044            }
3045        ]
3046    );
3047
3048    assert_eq!(
3049        buffer.diagnostic_group::<Point>(1).collect::<Vec<_>>(),
3050        &[
3051            DiagnosticEntry {
3052                range: Point::new(1, 8)..Point::new(1, 9),
3053                diagnostic: Diagnostic {
3054                    severity: DiagnosticSeverity::WARNING,
3055                    message: "error 1".to_string(),
3056                    group_id: 1,
3057                    is_primary: true,
3058                    ..Default::default()
3059                }
3060            },
3061            DiagnosticEntry {
3062                range: Point::new(1, 8)..Point::new(1, 9),
3063                diagnostic: Diagnostic {
3064                    severity: DiagnosticSeverity::HINT,
3065                    message: "error 1 hint 1".to_string(),
3066                    group_id: 1,
3067                    is_primary: false,
3068                    ..Default::default()
3069                }
3070            },
3071        ]
3072    );
3073}
3074
3075#[gpui::test]
3076async fn test_rename(cx: &mut gpui::TestAppContext) {
3077    cx.foreground().forbid_parking();
3078
3079    let mut language = Language::new(
3080        LanguageConfig {
3081            name: "Rust".into(),
3082            path_suffixes: vec!["rs".to_string()],
3083            ..Default::default()
3084        },
3085        Some(tree_sitter_rust::language()),
3086    );
3087    let mut fake_servers = language
3088        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3089            capabilities: lsp::ServerCapabilities {
3090                rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
3091                    prepare_provider: Some(true),
3092                    work_done_progress_options: Default::default(),
3093                })),
3094                ..Default::default()
3095            },
3096            ..Default::default()
3097        }))
3098        .await;
3099
3100    let fs = FakeFs::new(cx.background());
3101    fs.insert_tree(
3102        "/dir",
3103        json!({
3104            "one.rs": "const ONE: usize = 1;",
3105            "two.rs": "const TWO: usize = one::ONE + one::ONE;"
3106        }),
3107    )
3108    .await;
3109
3110    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
3111    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
3112    let buffer = project
3113        .update(cx, |project, cx| {
3114            project.open_local_buffer("/dir/one.rs", cx)
3115        })
3116        .await
3117        .unwrap();
3118
3119    let fake_server = fake_servers.next().await.unwrap();
3120
3121    let response = project.update(cx, |project, cx| {
3122        project.prepare_rename(buffer.clone(), 7, cx)
3123    });
3124    fake_server
3125        .handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
3126            assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
3127            assert_eq!(params.position, lsp::Position::new(0, 7));
3128            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
3129                lsp::Position::new(0, 6),
3130                lsp::Position::new(0, 9),
3131            ))))
3132        })
3133        .next()
3134        .await
3135        .unwrap();
3136    let range = response.await.unwrap().unwrap();
3137    let range = buffer.read_with(cx, |buffer, _| range.to_offset(buffer));
3138    assert_eq!(range, 6..9);
3139
3140    let response = project.update(cx, |project, cx| {
3141        project.perform_rename(buffer.clone(), 7, "THREE".to_string(), true, cx)
3142    });
3143    fake_server
3144        .handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
3145            assert_eq!(
3146                params.text_document_position.text_document.uri.as_str(),
3147                "file:///dir/one.rs"
3148            );
3149            assert_eq!(
3150                params.text_document_position.position,
3151                lsp::Position::new(0, 7)
3152            );
3153            assert_eq!(params.new_name, "THREE");
3154            Ok(Some(lsp::WorkspaceEdit {
3155                changes: Some(
3156                    [
3157                        (
3158                            lsp::Url::from_file_path("/dir/one.rs").unwrap(),
3159                            vec![lsp::TextEdit::new(
3160                                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
3161                                "THREE".to_string(),
3162                            )],
3163                        ),
3164                        (
3165                            lsp::Url::from_file_path("/dir/two.rs").unwrap(),
3166                            vec![
3167                                lsp::TextEdit::new(
3168                                    lsp::Range::new(
3169                                        lsp::Position::new(0, 24),
3170                                        lsp::Position::new(0, 27),
3171                                    ),
3172                                    "THREE".to_string(),
3173                                ),
3174                                lsp::TextEdit::new(
3175                                    lsp::Range::new(
3176                                        lsp::Position::new(0, 35),
3177                                        lsp::Position::new(0, 38),
3178                                    ),
3179                                    "THREE".to_string(),
3180                                ),
3181                            ],
3182                        ),
3183                    ]
3184                    .into_iter()
3185                    .collect(),
3186                ),
3187                ..Default::default()
3188            }))
3189        })
3190        .next()
3191        .await
3192        .unwrap();
3193    let mut transaction = response.await.unwrap().0;
3194    assert_eq!(transaction.len(), 2);
3195    assert_eq!(
3196        transaction
3197            .remove_entry(&buffer)
3198            .unwrap()
3199            .0
3200            .read_with(cx, |buffer, _| buffer.text()),
3201        "const THREE: usize = 1;"
3202    );
3203    assert_eq!(
3204        transaction
3205            .into_keys()
3206            .next()
3207            .unwrap()
3208            .read_with(cx, |buffer, _| buffer.text()),
3209        "const TWO: usize = one::THREE + one::THREE;"
3210    );
3211}
3212
3213#[gpui::test]
3214async fn test_search(cx: &mut gpui::TestAppContext) {
3215    let fs = FakeFs::new(cx.background());
3216    fs.insert_tree(
3217        "/dir",
3218        json!({
3219            "one.rs": "const ONE: usize = 1;",
3220            "two.rs": "const TWO: usize = one::ONE + one::ONE;",
3221            "three.rs": "const THREE: usize = one::ONE + two::TWO;",
3222            "four.rs": "const FOUR: usize = one::ONE + three::THREE;",
3223        }),
3224    )
3225    .await;
3226    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
3227    assert_eq!(
3228        search(&project, SearchQuery::text("TWO", false, true), cx)
3229            .await
3230            .unwrap(),
3231        HashMap::from_iter([
3232            ("two.rs".to_string(), vec![6..9]),
3233            ("three.rs".to_string(), vec![37..40])
3234        ])
3235    );
3236
3237    let buffer_4 = project
3238        .update(cx, |project, cx| {
3239            project.open_local_buffer("/dir/four.rs", cx)
3240        })
3241        .await
3242        .unwrap();
3243    buffer_4.update(cx, |buffer, cx| {
3244        let text = "two::TWO";
3245        buffer.edit([(20..28, text), (31..43, text)], None, cx);
3246    });
3247
3248    assert_eq!(
3249        search(&project, SearchQuery::text("TWO", false, true), cx)
3250            .await
3251            .unwrap(),
3252        HashMap::from_iter([
3253            ("two.rs".to_string(), vec![6..9]),
3254            ("three.rs".to_string(), vec![37..40]),
3255            ("four.rs".to_string(), vec![25..28, 36..39])
3256        ])
3257    );
3258
3259    async fn search(
3260        project: &ModelHandle<Project>,
3261        query: SearchQuery,
3262        cx: &mut gpui::TestAppContext,
3263    ) -> Result<HashMap<String, Vec<Range<usize>>>> {
3264        let results = project
3265            .update(cx, |project, cx| project.search(query, cx))
3266            .await?;
3267
3268        Ok(results
3269            .into_iter()
3270            .map(|(buffer, ranges)| {
3271                buffer.read_with(cx, |buffer, _| {
3272                    let path = buffer.file().unwrap().path().to_string_lossy().to_string();
3273                    let ranges = ranges
3274                        .into_iter()
3275                        .map(|range| range.to_offset(buffer))
3276                        .collect::<Vec<_>>();
3277                    (path, ranges)
3278                })
3279            })
3280            .collect())
3281    }
3282}