project_tests.rs

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