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                Some(lsp_document_version),
1577                cx,
1578            )
1579        })
1580        .await
1581        .unwrap();
1582
1583    buffer.update(cx, |buffer, cx| {
1584        for (range, new_text) in edits {
1585            buffer.edit([(range, new_text)], None, cx);
1586        }
1587        assert_eq!(
1588            buffer.text(),
1589            "
1590                // above first function
1591                fn a() {
1592                    // inside first function
1593                    f10();
1594                }
1595                fn b() {
1596                    // inside second function f200();
1597                }
1598                fn c() {
1599                    f4000();
1600                }
1601                "
1602            .unindent()
1603        );
1604    });
1605}
1606
1607#[gpui::test]
1608async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestAppContext) {
1609    cx.foreground().forbid_parking();
1610
1611    let text = "
1612        use a::b;
1613        use a::c;
1614
1615        fn f() {
1616            b();
1617            c();
1618        }
1619    "
1620    .unindent();
1621
1622    let fs = FakeFs::new(cx.background());
1623    fs.insert_tree(
1624        "/dir",
1625        json!({
1626            "a.rs": text.clone(),
1627        }),
1628    )
1629    .await;
1630
1631    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
1632    let buffer = project
1633        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
1634        .await
1635        .unwrap();
1636
1637    // Simulate the language server sending us a small edit in the form of a very large diff.
1638    // Rust-analyzer does this when performing a merge-imports code action.
1639    let edits = project
1640        .update(cx, |project, cx| {
1641            project.edits_from_lsp(
1642                &buffer,
1643                [
1644                    // Replace the first use statement without editing the semicolon.
1645                    lsp::TextEdit {
1646                        range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 8)),
1647                        new_text: "a::{b, c}".into(),
1648                    },
1649                    // Reinsert the remainder of the file between the semicolon and the final
1650                    // newline of the file.
1651                    lsp::TextEdit {
1652                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 9)),
1653                        new_text: "\n\n".into(),
1654                    },
1655                    lsp::TextEdit {
1656                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 9)),
1657                        new_text: "
1658                            fn f() {
1659                                b();
1660                                c();
1661                            }"
1662                        .unindent(),
1663                    },
1664                    // Delete everything after the first newline of the file.
1665                    lsp::TextEdit {
1666                        range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(7, 0)),
1667                        new_text: "".into(),
1668                    },
1669                ],
1670                None,
1671                cx,
1672            )
1673        })
1674        .await
1675        .unwrap();
1676
1677    buffer.update(cx, |buffer, cx| {
1678        let edits = edits
1679            .into_iter()
1680            .map(|(range, text)| {
1681                (
1682                    range.start.to_point(buffer)..range.end.to_point(buffer),
1683                    text,
1684                )
1685            })
1686            .collect::<Vec<_>>();
1687
1688        assert_eq!(
1689            edits,
1690            [
1691                (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()),
1692                (Point::new(1, 0)..Point::new(2, 0), "".into())
1693            ]
1694        );
1695
1696        for (range, new_text) in edits {
1697            buffer.edit([(range, new_text)], None, cx);
1698        }
1699        assert_eq!(
1700            buffer.text(),
1701            "
1702                use a::{b, c};
1703
1704                fn f() {
1705                    b();
1706                    c();
1707                }
1708            "
1709            .unindent()
1710        );
1711    });
1712}
1713
1714#[gpui::test]
1715async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) {
1716    cx.foreground().forbid_parking();
1717
1718    let text = "
1719        use a::b;
1720        use a::c;
1721
1722        fn f() {
1723            b();
1724            c();
1725        }
1726    "
1727    .unindent();
1728
1729    let fs = FakeFs::new(cx.background());
1730    fs.insert_tree(
1731        "/dir",
1732        json!({
1733            "a.rs": text.clone(),
1734        }),
1735    )
1736    .await;
1737
1738    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
1739    let buffer = project
1740        .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
1741        .await
1742        .unwrap();
1743
1744    // Simulate the language server sending us edits in a non-ordered fashion,
1745    // with ranges sometimes being inverted or pointing to invalid locations.
1746    let edits = project
1747        .update(cx, |project, cx| {
1748            project.edits_from_lsp(
1749                &buffer,
1750                [
1751                    lsp::TextEdit {
1752                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 9)),
1753                        new_text: "\n\n".into(),
1754                    },
1755                    lsp::TextEdit {
1756                        range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 4)),
1757                        new_text: "a::{b, c}".into(),
1758                    },
1759                    lsp::TextEdit {
1760                        range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(99, 0)),
1761                        new_text: "".into(),
1762                    },
1763                    lsp::TextEdit {
1764                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 9)),
1765                        new_text: "
1766                            fn f() {
1767                                b();
1768                                c();
1769                            }"
1770                        .unindent(),
1771                    },
1772                ],
1773                None,
1774                cx,
1775            )
1776        })
1777        .await
1778        .unwrap();
1779
1780    buffer.update(cx, |buffer, cx| {
1781        let edits = edits
1782            .into_iter()
1783            .map(|(range, text)| {
1784                (
1785                    range.start.to_point(buffer)..range.end.to_point(buffer),
1786                    text,
1787                )
1788            })
1789            .collect::<Vec<_>>();
1790
1791        assert_eq!(
1792            edits,
1793            [
1794                (Point::new(0, 4)..Point::new(0, 8), "a::{b, c}".into()),
1795                (Point::new(1, 0)..Point::new(2, 0), "".into())
1796            ]
1797        );
1798
1799        for (range, new_text) in edits {
1800            buffer.edit([(range, new_text)], None, cx);
1801        }
1802        assert_eq!(
1803            buffer.text(),
1804            "
1805                use a::{b, c};
1806
1807                fn f() {
1808                    b();
1809                    c();
1810                }
1811            "
1812            .unindent()
1813        );
1814    });
1815}
1816
1817fn chunks_with_diagnostics<T: ToOffset + ToPoint>(
1818    buffer: &Buffer,
1819    range: Range<T>,
1820) -> Vec<(String, Option<DiagnosticSeverity>)> {
1821    let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = Vec::new();
1822    for chunk in buffer.snapshot().chunks(range, true) {
1823        if chunks.last().map_or(false, |prev_chunk| {
1824            prev_chunk.1 == chunk.diagnostic_severity
1825        }) {
1826            chunks.last_mut().unwrap().0.push_str(chunk.text);
1827        } else {
1828            chunks.push((chunk.text.to_string(), chunk.diagnostic_severity));
1829        }
1830    }
1831    chunks
1832}
1833
1834#[gpui::test(iterations = 10)]
1835async fn test_definition(cx: &mut gpui::TestAppContext) {
1836    let mut language = Language::new(
1837        LanguageConfig {
1838            name: "Rust".into(),
1839            path_suffixes: vec!["rs".to_string()],
1840            ..Default::default()
1841        },
1842        Some(tree_sitter_rust::language()),
1843    );
1844    let mut fake_servers = language.set_fake_lsp_adapter(Default::default()).await;
1845
1846    let fs = FakeFs::new(cx.background());
1847    fs.insert_tree(
1848        "/dir",
1849        json!({
1850            "a.rs": "const fn a() { A }",
1851            "b.rs": "const y: i32 = crate::a()",
1852        }),
1853    )
1854    .await;
1855
1856    let project = Project::test(fs, ["/dir/b.rs".as_ref()], cx).await;
1857    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
1858
1859    let buffer = project
1860        .update(cx, |project, cx| project.open_local_buffer("/dir/b.rs", cx))
1861        .await
1862        .unwrap();
1863
1864    let fake_server = fake_servers.next().await.unwrap();
1865    fake_server.handle_request::<lsp::request::GotoDefinition, _, _>(|params, _| async move {
1866        let params = params.text_document_position_params;
1867        assert_eq!(
1868            params.text_document.uri.to_file_path().unwrap(),
1869            Path::new("/dir/b.rs"),
1870        );
1871        assert_eq!(params.position, lsp::Position::new(0, 22));
1872
1873        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
1874            lsp::Location::new(
1875                lsp::Url::from_file_path("/dir/a.rs").unwrap(),
1876                lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
1877            ),
1878        )))
1879    });
1880
1881    let mut definitions = project
1882        .update(cx, |project, cx| project.definition(&buffer, 22, cx))
1883        .await
1884        .unwrap();
1885
1886    // Assert no new language server started
1887    cx.foreground().run_until_parked();
1888    assert!(fake_servers.try_next().is_err());
1889
1890    assert_eq!(definitions.len(), 1);
1891    let definition = definitions.pop().unwrap();
1892    cx.update(|cx| {
1893        let target_buffer = definition.target.buffer.read(cx);
1894        assert_eq!(
1895            target_buffer
1896                .file()
1897                .unwrap()
1898                .as_local()
1899                .unwrap()
1900                .abs_path(cx),
1901            Path::new("/dir/a.rs"),
1902        );
1903        assert_eq!(definition.target.range.to_offset(target_buffer), 9..10);
1904        assert_eq!(
1905            list_worktrees(&project, cx),
1906            [("/dir/b.rs".as_ref(), true), ("/dir/a.rs".as_ref(), false)]
1907        );
1908
1909        drop(definition);
1910    });
1911    cx.read(|cx| {
1912        assert_eq!(list_worktrees(&project, cx), [("/dir/b.rs".as_ref(), true)]);
1913    });
1914
1915    fn list_worktrees<'a>(
1916        project: &'a ModelHandle<Project>,
1917        cx: &'a AppContext,
1918    ) -> Vec<(&'a Path, bool)> {
1919        project
1920            .read(cx)
1921            .worktrees(cx)
1922            .map(|worktree| {
1923                let worktree = worktree.read(cx);
1924                (
1925                    worktree.as_local().unwrap().abs_path().as_ref(),
1926                    worktree.is_visible(),
1927                )
1928            })
1929            .collect::<Vec<_>>()
1930    }
1931}
1932
1933#[gpui::test]
1934async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
1935    let mut language = Language::new(
1936        LanguageConfig {
1937            name: "TypeScript".into(),
1938            path_suffixes: vec!["ts".to_string()],
1939            ..Default::default()
1940        },
1941        Some(tree_sitter_typescript::language_typescript()),
1942    );
1943    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
1944
1945    let fs = FakeFs::new(cx.background());
1946    fs.insert_tree(
1947        "/dir",
1948        json!({
1949            "a.ts": "",
1950        }),
1951    )
1952    .await;
1953
1954    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
1955    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
1956    let buffer = project
1957        .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
1958        .await
1959        .unwrap();
1960
1961    let fake_server = fake_language_servers.next().await.unwrap();
1962
1963    let text = "let a = b.fqn";
1964    buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
1965    let completions = project.update(cx, |project, cx| {
1966        project.completions(&buffer, text.len(), cx)
1967    });
1968
1969    fake_server
1970        .handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
1971            Ok(Some(lsp::CompletionResponse::Array(vec![
1972                lsp::CompletionItem {
1973                    label: "fullyQualifiedName?".into(),
1974                    insert_text: Some("fullyQualifiedName".into()),
1975                    ..Default::default()
1976                },
1977            ])))
1978        })
1979        .next()
1980        .await;
1981    let completions = completions.await.unwrap();
1982    let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
1983    assert_eq!(completions.len(), 1);
1984    assert_eq!(completions[0].new_text, "fullyQualifiedName");
1985    assert_eq!(
1986        completions[0].old_range.to_offset(&snapshot),
1987        text.len() - 3..text.len()
1988    );
1989
1990    let text = "let a = \"atoms/cmp\"";
1991    buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
1992    let completions = project.update(cx, |project, cx| {
1993        project.completions(&buffer, text.len() - 1, cx)
1994    });
1995
1996    fake_server
1997        .handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
1998            Ok(Some(lsp::CompletionResponse::Array(vec![
1999                lsp::CompletionItem {
2000                    label: "component".into(),
2001                    ..Default::default()
2002                },
2003            ])))
2004        })
2005        .next()
2006        .await;
2007    let completions = completions.await.unwrap();
2008    let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
2009    assert_eq!(completions.len(), 1);
2010    assert_eq!(completions[0].new_text, "component");
2011    assert_eq!(
2012        completions[0].old_range.to_offset(&snapshot),
2013        text.len() - 4..text.len() - 1
2014    );
2015}
2016
2017#[gpui::test]
2018async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
2019    let mut language = Language::new(
2020        LanguageConfig {
2021            name: "TypeScript".into(),
2022            path_suffixes: vec!["ts".to_string()],
2023            ..Default::default()
2024        },
2025        Some(tree_sitter_typescript::language_typescript()),
2026    );
2027    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2028
2029    let fs = FakeFs::new(cx.background());
2030    fs.insert_tree(
2031        "/dir",
2032        json!({
2033            "a.ts": "",
2034        }),
2035    )
2036    .await;
2037
2038    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
2039    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
2040    let buffer = project
2041        .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
2042        .await
2043        .unwrap();
2044
2045    let fake_server = fake_language_servers.next().await.unwrap();
2046
2047    let text = "let a = b.fqn";
2048    buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
2049    let completions = project.update(cx, |project, cx| {
2050        project.completions(&buffer, text.len(), cx)
2051    });
2052
2053    fake_server
2054        .handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
2055            Ok(Some(lsp::CompletionResponse::Array(vec![
2056                lsp::CompletionItem {
2057                    label: "fullyQualifiedName?".into(),
2058                    insert_text: Some("fully\rQualified\r\nName".into()),
2059                    ..Default::default()
2060                },
2061            ])))
2062        })
2063        .next()
2064        .await;
2065    let completions = completions.await.unwrap();
2066    assert_eq!(completions.len(), 1);
2067    assert_eq!(completions[0].new_text, "fully\nQualified\nName");
2068}
2069
2070#[gpui::test(iterations = 10)]
2071async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
2072    let mut language = Language::new(
2073        LanguageConfig {
2074            name: "TypeScript".into(),
2075            path_suffixes: vec!["ts".to_string()],
2076            ..Default::default()
2077        },
2078        None,
2079    );
2080    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2081
2082    let fs = FakeFs::new(cx.background());
2083    fs.insert_tree(
2084        "/dir",
2085        json!({
2086            "a.ts": "a",
2087        }),
2088    )
2089    .await;
2090
2091    let project = Project::test(fs, ["/dir".as_ref()], cx).await;
2092    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
2093    let buffer = project
2094        .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
2095        .await
2096        .unwrap();
2097
2098    let fake_server = fake_language_servers.next().await.unwrap();
2099
2100    // Language server returns code actions that contain commands, and not edits.
2101    let actions = project.update(cx, |project, cx| project.code_actions(&buffer, 0..0, cx));
2102    fake_server
2103        .handle_request::<lsp::request::CodeActionRequest, _, _>(|_, _| async move {
2104            Ok(Some(vec![
2105                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
2106                    title: "The code action".into(),
2107                    command: Some(lsp::Command {
2108                        title: "The command".into(),
2109                        command: "_the/command".into(),
2110                        arguments: Some(vec![json!("the-argument")]),
2111                    }),
2112                    ..Default::default()
2113                }),
2114                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
2115                    title: "two".into(),
2116                    ..Default::default()
2117                }),
2118            ]))
2119        })
2120        .next()
2121        .await;
2122
2123    let action = actions.await.unwrap()[0].clone();
2124    let apply = project.update(cx, |project, cx| {
2125        project.apply_code_action(buffer.clone(), action, true, cx)
2126    });
2127
2128    // Resolving the code action does not populate its edits. In absence of
2129    // edits, we must execute the given command.
2130    fake_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
2131        |action, _| async move { Ok(action) },
2132    );
2133
2134    // While executing the command, the language server sends the editor
2135    // a `workspaceEdit` request.
2136    fake_server
2137        .handle_request::<lsp::request::ExecuteCommand, _, _>({
2138            let fake = fake_server.clone();
2139            move |params, _| {
2140                assert_eq!(params.command, "_the/command");
2141                let fake = fake.clone();
2142                async move {
2143                    fake.server
2144                        .request::<lsp::request::ApplyWorkspaceEdit>(
2145                            lsp::ApplyWorkspaceEditParams {
2146                                label: None,
2147                                edit: lsp::WorkspaceEdit {
2148                                    changes: Some(
2149                                        [(
2150                                            lsp::Url::from_file_path("/dir/a.ts").unwrap(),
2151                                            vec![lsp::TextEdit {
2152                                                range: lsp::Range::new(
2153                                                    lsp::Position::new(0, 0),
2154                                                    lsp::Position::new(0, 0),
2155                                                ),
2156                                                new_text: "X".into(),
2157                                            }],
2158                                        )]
2159                                        .into_iter()
2160                                        .collect(),
2161                                    ),
2162                                    ..Default::default()
2163                                },
2164                            },
2165                        )
2166                        .await
2167                        .unwrap();
2168                    Ok(Some(json!(null)))
2169                }
2170            }
2171        })
2172        .next()
2173        .await;
2174
2175    // Applying the code action returns a project transaction containing the edits
2176    // sent by the language server in its `workspaceEdit` request.
2177    let transaction = apply.await.unwrap();
2178    assert!(transaction.0.contains_key(&buffer));
2179    buffer.update(cx, |buffer, cx| {
2180        assert_eq!(buffer.text(), "Xa");
2181        buffer.undo(cx);
2182        assert_eq!(buffer.text(), "a");
2183    });
2184}
2185
2186#[gpui::test]
2187async fn test_save_file(cx: &mut gpui::TestAppContext) {
2188    let fs = FakeFs::new(cx.background());
2189    fs.insert_tree(
2190        "/dir",
2191        json!({
2192            "file1": "the old contents",
2193        }),
2194    )
2195    .await;
2196
2197    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
2198    let buffer = project
2199        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
2200        .await
2201        .unwrap();
2202    buffer.update(cx, |buffer, cx| {
2203        assert_eq!(buffer.text(), "the old contents");
2204        buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx);
2205    });
2206
2207    project
2208        .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
2209        .await
2210        .unwrap();
2211
2212    let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
2213    assert_eq!(new_text, buffer.read_with(cx, |buffer, _| buffer.text()));
2214}
2215
2216#[gpui::test]
2217async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) {
2218    let fs = FakeFs::new(cx.background());
2219    fs.insert_tree(
2220        "/dir",
2221        json!({
2222            "file1": "the old contents",
2223        }),
2224    )
2225    .await;
2226
2227    let project = Project::test(fs.clone(), ["/dir/file1".as_ref()], cx).await;
2228    let buffer = project
2229        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
2230        .await
2231        .unwrap();
2232    buffer.update(cx, |buffer, cx| {
2233        buffer.edit([(0..0, "a line of text.\n".repeat(10 * 1024))], None, cx);
2234    });
2235
2236    project
2237        .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
2238        .await
2239        .unwrap();
2240
2241    let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
2242    assert_eq!(new_text, buffer.read_with(cx, |buffer, _| buffer.text()));
2243}
2244
2245#[gpui::test]
2246async fn test_save_as(cx: &mut gpui::TestAppContext) {
2247    let fs = FakeFs::new(cx.background());
2248    fs.insert_tree("/dir", json!({})).await;
2249
2250    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
2251
2252    let languages = project.read_with(cx, |project, _| project.languages().clone());
2253    languages.register(
2254        "/some/path",
2255        LanguageConfig {
2256            name: "Rust".into(),
2257            path_suffixes: vec!["rs".into()],
2258            ..Default::default()
2259        },
2260        tree_sitter_rust::language(),
2261        None,
2262        |_| Default::default(),
2263    );
2264
2265    let buffer = project.update(cx, |project, cx| {
2266        project.create_buffer("", None, cx).unwrap()
2267    });
2268    buffer.update(cx, |buffer, cx| {
2269        buffer.edit([(0..0, "abc")], None, cx);
2270        assert!(buffer.is_dirty());
2271        assert!(!buffer.has_conflict());
2272        assert_eq!(buffer.language().unwrap().name().as_ref(), "Plain Text");
2273    });
2274    project
2275        .update(cx, |project, cx| {
2276            project.save_buffer_as(buffer.clone(), "/dir/file1.rs".into(), cx)
2277        })
2278        .await
2279        .unwrap();
2280    assert_eq!(fs.load(Path::new("/dir/file1.rs")).await.unwrap(), "abc");
2281
2282    cx.foreground().run_until_parked();
2283    buffer.read_with(cx, |buffer, cx| {
2284        assert_eq!(
2285            buffer.file().unwrap().full_path(cx),
2286            Path::new("dir/file1.rs")
2287        );
2288        assert!(!buffer.is_dirty());
2289        assert!(!buffer.has_conflict());
2290        assert_eq!(buffer.language().unwrap().name().as_ref(), "Rust");
2291    });
2292
2293    let opened_buffer = project
2294        .update(cx, |project, cx| {
2295            project.open_local_buffer("/dir/file1.rs", cx)
2296        })
2297        .await
2298        .unwrap();
2299    assert_eq!(opened_buffer, buffer);
2300}
2301
2302#[gpui::test(retries = 5)]
2303async fn test_rescan_and_remote_updates(
2304    deterministic: Arc<Deterministic>,
2305    cx: &mut gpui::TestAppContext,
2306) {
2307    let dir = temp_tree(json!({
2308        "a": {
2309            "file1": "",
2310            "file2": "",
2311            "file3": "",
2312        },
2313        "b": {
2314            "c": {
2315                "file4": "",
2316                "file5": "",
2317            }
2318        }
2319    }));
2320
2321    let project = Project::test(Arc::new(RealFs), [dir.path()], cx).await;
2322    let rpc = project.read_with(cx, |p, _| p.client.clone());
2323
2324    let buffer_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| {
2325        let buffer = project.update(cx, |p, cx| p.open_local_buffer(dir.path().join(path), cx));
2326        async move { buffer.await.unwrap() }
2327    };
2328    let id_for_path = |path: &'static str, cx: &gpui::TestAppContext| {
2329        project.read_with(cx, |project, cx| {
2330            let tree = project.worktrees(cx).next().unwrap();
2331            tree.read(cx)
2332                .entry_for_path(path)
2333                .unwrap_or_else(|| panic!("no entry for path {}", path))
2334                .id
2335        })
2336    };
2337
2338    let buffer2 = buffer_for_path("a/file2", cx).await;
2339    let buffer3 = buffer_for_path("a/file3", cx).await;
2340    let buffer4 = buffer_for_path("b/c/file4", cx).await;
2341    let buffer5 = buffer_for_path("b/c/file5", cx).await;
2342
2343    let file2_id = id_for_path("a/file2", cx);
2344    let file3_id = id_for_path("a/file3", cx);
2345    let file4_id = id_for_path("b/c/file4", cx);
2346
2347    // Create a remote copy of this worktree.
2348    let tree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap());
2349    let initial_snapshot = tree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
2350    let remote = cx.update(|cx| {
2351        Worktree::remote(
2352            1,
2353            1,
2354            proto::WorktreeMetadata {
2355                id: initial_snapshot.id().to_proto(),
2356                root_name: initial_snapshot.root_name().into(),
2357                abs_path: initial_snapshot
2358                    .abs_path()
2359                    .as_os_str()
2360                    .to_string_lossy()
2361                    .into(),
2362                visible: true,
2363            },
2364            rpc.clone(),
2365            cx,
2366        )
2367    });
2368    remote.update(cx, |remote, _| {
2369        let update = initial_snapshot.build_initial_update(1);
2370        remote.as_remote_mut().unwrap().update_from_remote(update);
2371    });
2372    deterministic.run_until_parked();
2373
2374    cx.read(|cx| {
2375        assert!(!buffer2.read(cx).is_dirty());
2376        assert!(!buffer3.read(cx).is_dirty());
2377        assert!(!buffer4.read(cx).is_dirty());
2378        assert!(!buffer5.read(cx).is_dirty());
2379    });
2380
2381    // Rename and delete files and directories.
2382    tree.flush_fs_events(cx).await;
2383    std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
2384    std::fs::remove_file(dir.path().join("b/c/file5")).unwrap();
2385    std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
2386    std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap();
2387    tree.flush_fs_events(cx).await;
2388
2389    let expected_paths = vec![
2390        "a",
2391        "a/file1",
2392        "a/file2.new",
2393        "b",
2394        "d",
2395        "d/file3",
2396        "d/file4",
2397    ];
2398
2399    cx.read(|app| {
2400        assert_eq!(
2401            tree.read(app)
2402                .paths()
2403                .map(|p| p.to_str().unwrap())
2404                .collect::<Vec<_>>(),
2405            expected_paths
2406        );
2407
2408        assert_eq!(id_for_path("a/file2.new", cx), file2_id);
2409        assert_eq!(id_for_path("d/file3", cx), file3_id);
2410        assert_eq!(id_for_path("d/file4", cx), file4_id);
2411
2412        assert_eq!(
2413            buffer2.read(app).file().unwrap().path().as_ref(),
2414            Path::new("a/file2.new")
2415        );
2416        assert_eq!(
2417            buffer3.read(app).file().unwrap().path().as_ref(),
2418            Path::new("d/file3")
2419        );
2420        assert_eq!(
2421            buffer4.read(app).file().unwrap().path().as_ref(),
2422            Path::new("d/file4")
2423        );
2424        assert_eq!(
2425            buffer5.read(app).file().unwrap().path().as_ref(),
2426            Path::new("b/c/file5")
2427        );
2428
2429        assert!(!buffer2.read(app).file().unwrap().is_deleted());
2430        assert!(!buffer3.read(app).file().unwrap().is_deleted());
2431        assert!(!buffer4.read(app).file().unwrap().is_deleted());
2432        assert!(buffer5.read(app).file().unwrap().is_deleted());
2433    });
2434
2435    // Update the remote worktree. Check that it becomes consistent with the
2436    // local worktree.
2437    remote.update(cx, |remote, cx| {
2438        let update = tree.read(cx).as_local().unwrap().snapshot().build_update(
2439            &initial_snapshot,
2440            1,
2441            1,
2442            true,
2443        );
2444        remote.as_remote_mut().unwrap().update_from_remote(update);
2445    });
2446    deterministic.run_until_parked();
2447    remote.read_with(cx, |remote, _| {
2448        assert_eq!(
2449            remote
2450                .paths()
2451                .map(|p| p.to_str().unwrap())
2452                .collect::<Vec<_>>(),
2453            expected_paths
2454        );
2455    });
2456}
2457
2458#[gpui::test(iterations = 10)]
2459async fn test_buffer_identity_across_renames(
2460    deterministic: Arc<Deterministic>,
2461    cx: &mut gpui::TestAppContext,
2462) {
2463    let fs = FakeFs::new(cx.background());
2464    fs.insert_tree(
2465        "/dir",
2466        json!({
2467            "a": {
2468                "file1": "",
2469            }
2470        }),
2471    )
2472    .await;
2473
2474    let project = Project::test(fs, [Path::new("/dir")], cx).await;
2475    let tree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap());
2476    let tree_id = tree.read_with(cx, |tree, _| tree.id());
2477
2478    let id_for_path = |path: &'static str, cx: &gpui::TestAppContext| {
2479        project.read_with(cx, |project, cx| {
2480            let tree = project.worktrees(cx).next().unwrap();
2481            tree.read(cx)
2482                .entry_for_path(path)
2483                .unwrap_or_else(|| panic!("no entry for path {}", path))
2484                .id
2485        })
2486    };
2487
2488    let dir_id = id_for_path("a", cx);
2489    let file_id = id_for_path("a/file1", cx);
2490    let buffer = project
2491        .update(cx, |p, cx| p.open_buffer((tree_id, "a/file1"), cx))
2492        .await
2493        .unwrap();
2494    buffer.read_with(cx, |buffer, _| assert!(!buffer.is_dirty()));
2495
2496    project
2497        .update(cx, |project, cx| {
2498            project.rename_entry(dir_id, Path::new("b"), cx)
2499        })
2500        .unwrap()
2501        .await
2502        .unwrap();
2503    deterministic.run_until_parked();
2504    assert_eq!(id_for_path("b", cx), dir_id);
2505    assert_eq!(id_for_path("b/file1", cx), file_id);
2506    buffer.read_with(cx, |buffer, _| assert!(!buffer.is_dirty()));
2507}
2508
2509#[gpui::test]
2510async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) {
2511    let fs = FakeFs::new(cx.background());
2512    fs.insert_tree(
2513        "/dir",
2514        json!({
2515            "a.txt": "a-contents",
2516            "b.txt": "b-contents",
2517        }),
2518    )
2519    .await;
2520
2521    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
2522
2523    // Spawn multiple tasks to open paths, repeating some paths.
2524    let (buffer_a_1, buffer_b, buffer_a_2) = project.update(cx, |p, cx| {
2525        (
2526            p.open_local_buffer("/dir/a.txt", cx),
2527            p.open_local_buffer("/dir/b.txt", cx),
2528            p.open_local_buffer("/dir/a.txt", cx),
2529        )
2530    });
2531
2532    let buffer_a_1 = buffer_a_1.await.unwrap();
2533    let buffer_a_2 = buffer_a_2.await.unwrap();
2534    let buffer_b = buffer_b.await.unwrap();
2535    assert_eq!(buffer_a_1.read_with(cx, |b, _| b.text()), "a-contents");
2536    assert_eq!(buffer_b.read_with(cx, |b, _| b.text()), "b-contents");
2537
2538    // There is only one buffer per path.
2539    let buffer_a_id = buffer_a_1.id();
2540    assert_eq!(buffer_a_2.id(), buffer_a_id);
2541
2542    // Open the same path again while it is still open.
2543    drop(buffer_a_1);
2544    let buffer_a_3 = project
2545        .update(cx, |p, cx| p.open_local_buffer("/dir/a.txt", cx))
2546        .await
2547        .unwrap();
2548
2549    // There's still only one buffer per path.
2550    assert_eq!(buffer_a_3.id(), buffer_a_id);
2551}
2552
2553#[gpui::test]
2554async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
2555    let fs = FakeFs::new(cx.background());
2556    fs.insert_tree(
2557        "/dir",
2558        json!({
2559            "file1": "abc",
2560            "file2": "def",
2561            "file3": "ghi",
2562        }),
2563    )
2564    .await;
2565
2566    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
2567
2568    let buffer1 = project
2569        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
2570        .await
2571        .unwrap();
2572    let events = Rc::new(RefCell::new(Vec::new()));
2573
2574    // initially, the buffer isn't dirty.
2575    buffer1.update(cx, |buffer, cx| {
2576        cx.subscribe(&buffer1, {
2577            let events = events.clone();
2578            move |_, _, event, _| match event {
2579                BufferEvent::Operation(_) => {}
2580                _ => events.borrow_mut().push(event.clone()),
2581            }
2582        })
2583        .detach();
2584
2585        assert!(!buffer.is_dirty());
2586        assert!(events.borrow().is_empty());
2587
2588        buffer.edit([(1..2, "")], None, cx);
2589    });
2590
2591    // after the first edit, the buffer is dirty, and emits a dirtied event.
2592    buffer1.update(cx, |buffer, cx| {
2593        assert!(buffer.text() == "ac");
2594        assert!(buffer.is_dirty());
2595        assert_eq!(
2596            *events.borrow(),
2597            &[language::Event::Edited, language::Event::DirtyChanged]
2598        );
2599        events.borrow_mut().clear();
2600        buffer.did_save(
2601            buffer.version(),
2602            buffer.as_rope().fingerprint(),
2603            buffer.file().unwrap().mtime(),
2604            cx,
2605        );
2606    });
2607
2608    // after saving, the buffer is not dirty, and emits a saved event.
2609    buffer1.update(cx, |buffer, cx| {
2610        assert!(!buffer.is_dirty());
2611        assert_eq!(*events.borrow(), &[language::Event::Saved]);
2612        events.borrow_mut().clear();
2613
2614        buffer.edit([(1..1, "B")], None, cx);
2615        buffer.edit([(2..2, "D")], None, cx);
2616    });
2617
2618    // after editing again, the buffer is dirty, and emits another dirty event.
2619    buffer1.update(cx, |buffer, cx| {
2620        assert!(buffer.text() == "aBDc");
2621        assert!(buffer.is_dirty());
2622        assert_eq!(
2623            *events.borrow(),
2624            &[
2625                language::Event::Edited,
2626                language::Event::DirtyChanged,
2627                language::Event::Edited,
2628            ],
2629        );
2630        events.borrow_mut().clear();
2631
2632        // After restoring the buffer to its previously-saved state,
2633        // the buffer is not considered dirty anymore.
2634        buffer.edit([(1..3, "")], None, cx);
2635        assert!(buffer.text() == "ac");
2636        assert!(!buffer.is_dirty());
2637    });
2638
2639    assert_eq!(
2640        *events.borrow(),
2641        &[language::Event::Edited, language::Event::DirtyChanged]
2642    );
2643
2644    // When a file is deleted, the buffer is considered dirty.
2645    let events = Rc::new(RefCell::new(Vec::new()));
2646    let buffer2 = project
2647        .update(cx, |p, cx| p.open_local_buffer("/dir/file2", cx))
2648        .await
2649        .unwrap();
2650    buffer2.update(cx, |_, cx| {
2651        cx.subscribe(&buffer2, {
2652            let events = events.clone();
2653            move |_, _, event, _| events.borrow_mut().push(event.clone())
2654        })
2655        .detach();
2656    });
2657
2658    fs.remove_file("/dir/file2".as_ref(), Default::default())
2659        .await
2660        .unwrap();
2661    cx.foreground().run_until_parked();
2662    buffer2.read_with(cx, |buffer, _| assert!(buffer.is_dirty()));
2663    assert_eq!(
2664        *events.borrow(),
2665        &[
2666            language::Event::DirtyChanged,
2667            language::Event::FileHandleChanged
2668        ]
2669    );
2670
2671    // When a file is already dirty when deleted, we don't emit a Dirtied event.
2672    let events = Rc::new(RefCell::new(Vec::new()));
2673    let buffer3 = project
2674        .update(cx, |p, cx| p.open_local_buffer("/dir/file3", cx))
2675        .await
2676        .unwrap();
2677    buffer3.update(cx, |_, cx| {
2678        cx.subscribe(&buffer3, {
2679            let events = events.clone();
2680            move |_, _, event, _| events.borrow_mut().push(event.clone())
2681        })
2682        .detach();
2683    });
2684
2685    buffer3.update(cx, |buffer, cx| {
2686        buffer.edit([(0..0, "x")], None, cx);
2687    });
2688    events.borrow_mut().clear();
2689    fs.remove_file("/dir/file3".as_ref(), Default::default())
2690        .await
2691        .unwrap();
2692    cx.foreground().run_until_parked();
2693    assert_eq!(*events.borrow(), &[language::Event::FileHandleChanged]);
2694    cx.read(|cx| assert!(buffer3.read(cx).is_dirty()));
2695}
2696
2697#[gpui::test]
2698async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
2699    let initial_contents = "aaa\nbbbbb\nc\n";
2700    let fs = FakeFs::new(cx.background());
2701    fs.insert_tree(
2702        "/dir",
2703        json!({
2704            "the-file": initial_contents,
2705        }),
2706    )
2707    .await;
2708    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
2709    let buffer = project
2710        .update(cx, |p, cx| p.open_local_buffer("/dir/the-file", cx))
2711        .await
2712        .unwrap();
2713
2714    let anchors = (0..3)
2715        .map(|row| buffer.read_with(cx, |b, _| b.anchor_before(Point::new(row, 1))))
2716        .collect::<Vec<_>>();
2717
2718    // Change the file on disk, adding two new lines of text, and removing
2719    // one line.
2720    buffer.read_with(cx, |buffer, _| {
2721        assert!(!buffer.is_dirty());
2722        assert!(!buffer.has_conflict());
2723    });
2724    let new_contents = "AAAA\naaa\nBB\nbbbbb\n";
2725    fs.save(
2726        "/dir/the-file".as_ref(),
2727        &new_contents.into(),
2728        LineEnding::Unix,
2729    )
2730    .await
2731    .unwrap();
2732
2733    // Because the buffer was not modified, it is reloaded from disk. Its
2734    // contents are edited according to the diff between the old and new
2735    // file contents.
2736    cx.foreground().run_until_parked();
2737    buffer.update(cx, |buffer, _| {
2738        assert_eq!(buffer.text(), new_contents);
2739        assert!(!buffer.is_dirty());
2740        assert!(!buffer.has_conflict());
2741
2742        let anchor_positions = anchors
2743            .iter()
2744            .map(|anchor| anchor.to_point(&*buffer))
2745            .collect::<Vec<_>>();
2746        assert_eq!(
2747            anchor_positions,
2748            [Point::new(1, 1), Point::new(3, 1), Point::new(3, 5)]
2749        );
2750    });
2751
2752    // Modify the buffer
2753    buffer.update(cx, |buffer, cx| {
2754        buffer.edit([(0..0, " ")], None, cx);
2755        assert!(buffer.is_dirty());
2756        assert!(!buffer.has_conflict());
2757    });
2758
2759    // Change the file on disk again, adding blank lines to the beginning.
2760    fs.save(
2761        "/dir/the-file".as_ref(),
2762        &"\n\n\nAAAA\naaa\nBB\nbbbbb\n".into(),
2763        LineEnding::Unix,
2764    )
2765    .await
2766    .unwrap();
2767
2768    // Because the buffer is modified, it doesn't reload from disk, but is
2769    // marked as having a conflict.
2770    cx.foreground().run_until_parked();
2771    buffer.read_with(cx, |buffer, _| {
2772        assert!(buffer.has_conflict());
2773    });
2774}
2775
2776#[gpui::test]
2777async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) {
2778    let fs = FakeFs::new(cx.background());
2779    fs.insert_tree(
2780        "/dir",
2781        json!({
2782            "file1": "a\nb\nc\n",
2783            "file2": "one\r\ntwo\r\nthree\r\n",
2784        }),
2785    )
2786    .await;
2787
2788    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
2789    let buffer1 = project
2790        .update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
2791        .await
2792        .unwrap();
2793    let buffer2 = project
2794        .update(cx, |p, cx| p.open_local_buffer("/dir/file2", cx))
2795        .await
2796        .unwrap();
2797
2798    buffer1.read_with(cx, |buffer, _| {
2799        assert_eq!(buffer.text(), "a\nb\nc\n");
2800        assert_eq!(buffer.line_ending(), LineEnding::Unix);
2801    });
2802    buffer2.read_with(cx, |buffer, _| {
2803        assert_eq!(buffer.text(), "one\ntwo\nthree\n");
2804        assert_eq!(buffer.line_ending(), LineEnding::Windows);
2805    });
2806
2807    // Change a file's line endings on disk from unix to windows. The buffer's
2808    // state updates correctly.
2809    fs.save(
2810        "/dir/file1".as_ref(),
2811        &"aaa\nb\nc\n".into(),
2812        LineEnding::Windows,
2813    )
2814    .await
2815    .unwrap();
2816    cx.foreground().run_until_parked();
2817    buffer1.read_with(cx, |buffer, _| {
2818        assert_eq!(buffer.text(), "aaa\nb\nc\n");
2819        assert_eq!(buffer.line_ending(), LineEnding::Windows);
2820    });
2821
2822    // Save a file with windows line endings. The file is written correctly.
2823    buffer2.update(cx, |buffer, cx| {
2824        buffer.set_text("one\ntwo\nthree\nfour\n", cx);
2825    });
2826    project
2827        .update(cx, |project, cx| project.save_buffer(buffer2, cx))
2828        .await
2829        .unwrap();
2830    assert_eq!(
2831        fs.load("/dir/file2".as_ref()).await.unwrap(),
2832        "one\r\ntwo\r\nthree\r\nfour\r\n",
2833    );
2834}
2835
2836#[gpui::test]
2837async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
2838    cx.foreground().forbid_parking();
2839
2840    let fs = FakeFs::new(cx.background());
2841    fs.insert_tree(
2842        "/the-dir",
2843        json!({
2844            "a.rs": "
2845                fn foo(mut v: Vec<usize>) {
2846                    for x in &v {
2847                        v.push(1);
2848                    }
2849                }
2850            "
2851            .unindent(),
2852        }),
2853    )
2854    .await;
2855
2856    let project = Project::test(fs.clone(), ["/the-dir".as_ref()], cx).await;
2857    let buffer = project
2858        .update(cx, |p, cx| p.open_local_buffer("/the-dir/a.rs", cx))
2859        .await
2860        .unwrap();
2861
2862    let buffer_uri = Url::from_file_path("/the-dir/a.rs").unwrap();
2863    let message = lsp::PublishDiagnosticsParams {
2864        uri: buffer_uri.clone(),
2865        diagnostics: vec![
2866            lsp::Diagnostic {
2867                range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
2868                severity: Some(DiagnosticSeverity::WARNING),
2869                message: "error 1".to_string(),
2870                related_information: Some(vec![lsp::DiagnosticRelatedInformation {
2871                    location: lsp::Location {
2872                        uri: buffer_uri.clone(),
2873                        range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
2874                    },
2875                    message: "error 1 hint 1".to_string(),
2876                }]),
2877                ..Default::default()
2878            },
2879            lsp::Diagnostic {
2880                range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
2881                severity: Some(DiagnosticSeverity::HINT),
2882                message: "error 1 hint 1".to_string(),
2883                related_information: Some(vec![lsp::DiagnosticRelatedInformation {
2884                    location: lsp::Location {
2885                        uri: buffer_uri.clone(),
2886                        range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
2887                    },
2888                    message: "original diagnostic".to_string(),
2889                }]),
2890                ..Default::default()
2891            },
2892            lsp::Diagnostic {
2893                range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
2894                severity: Some(DiagnosticSeverity::ERROR),
2895                message: "error 2".to_string(),
2896                related_information: Some(vec![
2897                    lsp::DiagnosticRelatedInformation {
2898                        location: lsp::Location {
2899                            uri: buffer_uri.clone(),
2900                            range: lsp::Range::new(
2901                                lsp::Position::new(1, 13),
2902                                lsp::Position::new(1, 15),
2903                            ),
2904                        },
2905                        message: "error 2 hint 1".to_string(),
2906                    },
2907                    lsp::DiagnosticRelatedInformation {
2908                        location: lsp::Location {
2909                            uri: buffer_uri.clone(),
2910                            range: lsp::Range::new(
2911                                lsp::Position::new(1, 13),
2912                                lsp::Position::new(1, 15),
2913                            ),
2914                        },
2915                        message: "error 2 hint 2".to_string(),
2916                    },
2917                ]),
2918                ..Default::default()
2919            },
2920            lsp::Diagnostic {
2921                range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
2922                severity: Some(DiagnosticSeverity::HINT),
2923                message: "error 2 hint 1".to_string(),
2924                related_information: Some(vec![lsp::DiagnosticRelatedInformation {
2925                    location: lsp::Location {
2926                        uri: buffer_uri.clone(),
2927                        range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
2928                    },
2929                    message: "original diagnostic".to_string(),
2930                }]),
2931                ..Default::default()
2932            },
2933            lsp::Diagnostic {
2934                range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
2935                severity: Some(DiagnosticSeverity::HINT),
2936                message: "error 2 hint 2".to_string(),
2937                related_information: Some(vec![lsp::DiagnosticRelatedInformation {
2938                    location: lsp::Location {
2939                        uri: buffer_uri,
2940                        range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
2941                    },
2942                    message: "original diagnostic".to_string(),
2943                }]),
2944                ..Default::default()
2945            },
2946        ],
2947        version: None,
2948    };
2949
2950    project
2951        .update(cx, |p, cx| p.update_diagnostics(0, message, &[], cx))
2952        .unwrap();
2953    let buffer = buffer.read_with(cx, |buffer, _| buffer.snapshot());
2954
2955    assert_eq!(
2956        buffer
2957            .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
2958            .collect::<Vec<_>>(),
2959        &[
2960            DiagnosticEntry {
2961                range: Point::new(1, 8)..Point::new(1, 9),
2962                diagnostic: Diagnostic {
2963                    severity: DiagnosticSeverity::WARNING,
2964                    message: "error 1".to_string(),
2965                    group_id: 1,
2966                    is_primary: true,
2967                    ..Default::default()
2968                }
2969            },
2970            DiagnosticEntry {
2971                range: Point::new(1, 8)..Point::new(1, 9),
2972                diagnostic: Diagnostic {
2973                    severity: DiagnosticSeverity::HINT,
2974                    message: "error 1 hint 1".to_string(),
2975                    group_id: 1,
2976                    is_primary: false,
2977                    ..Default::default()
2978                }
2979            },
2980            DiagnosticEntry {
2981                range: Point::new(1, 13)..Point::new(1, 15),
2982                diagnostic: Diagnostic {
2983                    severity: DiagnosticSeverity::HINT,
2984                    message: "error 2 hint 1".to_string(),
2985                    group_id: 0,
2986                    is_primary: false,
2987                    ..Default::default()
2988                }
2989            },
2990            DiagnosticEntry {
2991                range: Point::new(1, 13)..Point::new(1, 15),
2992                diagnostic: Diagnostic {
2993                    severity: DiagnosticSeverity::HINT,
2994                    message: "error 2 hint 2".to_string(),
2995                    group_id: 0,
2996                    is_primary: false,
2997                    ..Default::default()
2998                }
2999            },
3000            DiagnosticEntry {
3001                range: Point::new(2, 8)..Point::new(2, 17),
3002                diagnostic: Diagnostic {
3003                    severity: DiagnosticSeverity::ERROR,
3004                    message: "error 2".to_string(),
3005                    group_id: 0,
3006                    is_primary: true,
3007                    ..Default::default()
3008                }
3009            }
3010        ]
3011    );
3012
3013    assert_eq!(
3014        buffer.diagnostic_group::<Point>(0).collect::<Vec<_>>(),
3015        &[
3016            DiagnosticEntry {
3017                range: Point::new(1, 13)..Point::new(1, 15),
3018                diagnostic: Diagnostic {
3019                    severity: DiagnosticSeverity::HINT,
3020                    message: "error 2 hint 1".to_string(),
3021                    group_id: 0,
3022                    is_primary: false,
3023                    ..Default::default()
3024                }
3025            },
3026            DiagnosticEntry {
3027                range: Point::new(1, 13)..Point::new(1, 15),
3028                diagnostic: Diagnostic {
3029                    severity: DiagnosticSeverity::HINT,
3030                    message: "error 2 hint 2".to_string(),
3031                    group_id: 0,
3032                    is_primary: false,
3033                    ..Default::default()
3034                }
3035            },
3036            DiagnosticEntry {
3037                range: Point::new(2, 8)..Point::new(2, 17),
3038                diagnostic: Diagnostic {
3039                    severity: DiagnosticSeverity::ERROR,
3040                    message: "error 2".to_string(),
3041                    group_id: 0,
3042                    is_primary: true,
3043                    ..Default::default()
3044                }
3045            }
3046        ]
3047    );
3048
3049    assert_eq!(
3050        buffer.diagnostic_group::<Point>(1).collect::<Vec<_>>(),
3051        &[
3052            DiagnosticEntry {
3053                range: Point::new(1, 8)..Point::new(1, 9),
3054                diagnostic: Diagnostic {
3055                    severity: DiagnosticSeverity::WARNING,
3056                    message: "error 1".to_string(),
3057                    group_id: 1,
3058                    is_primary: true,
3059                    ..Default::default()
3060                }
3061            },
3062            DiagnosticEntry {
3063                range: Point::new(1, 8)..Point::new(1, 9),
3064                diagnostic: Diagnostic {
3065                    severity: DiagnosticSeverity::HINT,
3066                    message: "error 1 hint 1".to_string(),
3067                    group_id: 1,
3068                    is_primary: false,
3069                    ..Default::default()
3070                }
3071            },
3072        ]
3073    );
3074}
3075
3076#[gpui::test]
3077async fn test_rename(cx: &mut gpui::TestAppContext) {
3078    cx.foreground().forbid_parking();
3079
3080    let mut language = Language::new(
3081        LanguageConfig {
3082            name: "Rust".into(),
3083            path_suffixes: vec!["rs".to_string()],
3084            ..Default::default()
3085        },
3086        Some(tree_sitter_rust::language()),
3087    );
3088    let mut fake_servers = language
3089        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3090            capabilities: lsp::ServerCapabilities {
3091                rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
3092                    prepare_provider: Some(true),
3093                    work_done_progress_options: Default::default(),
3094                })),
3095                ..Default::default()
3096            },
3097            ..Default::default()
3098        }))
3099        .await;
3100
3101    let fs = FakeFs::new(cx.background());
3102    fs.insert_tree(
3103        "/dir",
3104        json!({
3105            "one.rs": "const ONE: usize = 1;",
3106            "two.rs": "const TWO: usize = one::ONE + one::ONE;"
3107        }),
3108    )
3109    .await;
3110
3111    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
3112    project.update(cx, |project, _| project.languages.add(Arc::new(language)));
3113    let buffer = project
3114        .update(cx, |project, cx| {
3115            project.open_local_buffer("/dir/one.rs", cx)
3116        })
3117        .await
3118        .unwrap();
3119
3120    let fake_server = fake_servers.next().await.unwrap();
3121
3122    let response = project.update(cx, |project, cx| {
3123        project.prepare_rename(buffer.clone(), 7, cx)
3124    });
3125    fake_server
3126        .handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
3127            assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
3128            assert_eq!(params.position, lsp::Position::new(0, 7));
3129            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
3130                lsp::Position::new(0, 6),
3131                lsp::Position::new(0, 9),
3132            ))))
3133        })
3134        .next()
3135        .await
3136        .unwrap();
3137    let range = response.await.unwrap().unwrap();
3138    let range = buffer.read_with(cx, |buffer, _| range.to_offset(buffer));
3139    assert_eq!(range, 6..9);
3140
3141    let response = project.update(cx, |project, cx| {
3142        project.perform_rename(buffer.clone(), 7, "THREE".to_string(), true, cx)
3143    });
3144    fake_server
3145        .handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
3146            assert_eq!(
3147                params.text_document_position.text_document.uri.as_str(),
3148                "file:///dir/one.rs"
3149            );
3150            assert_eq!(
3151                params.text_document_position.position,
3152                lsp::Position::new(0, 7)
3153            );
3154            assert_eq!(params.new_name, "THREE");
3155            Ok(Some(lsp::WorkspaceEdit {
3156                changes: Some(
3157                    [
3158                        (
3159                            lsp::Url::from_file_path("/dir/one.rs").unwrap(),
3160                            vec![lsp::TextEdit::new(
3161                                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
3162                                "THREE".to_string(),
3163                            )],
3164                        ),
3165                        (
3166                            lsp::Url::from_file_path("/dir/two.rs").unwrap(),
3167                            vec![
3168                                lsp::TextEdit::new(
3169                                    lsp::Range::new(
3170                                        lsp::Position::new(0, 24),
3171                                        lsp::Position::new(0, 27),
3172                                    ),
3173                                    "THREE".to_string(),
3174                                ),
3175                                lsp::TextEdit::new(
3176                                    lsp::Range::new(
3177                                        lsp::Position::new(0, 35),
3178                                        lsp::Position::new(0, 38),
3179                                    ),
3180                                    "THREE".to_string(),
3181                                ),
3182                            ],
3183                        ),
3184                    ]
3185                    .into_iter()
3186                    .collect(),
3187                ),
3188                ..Default::default()
3189            }))
3190        })
3191        .next()
3192        .await
3193        .unwrap();
3194    let mut transaction = response.await.unwrap().0;
3195    assert_eq!(transaction.len(), 2);
3196    assert_eq!(
3197        transaction
3198            .remove_entry(&buffer)
3199            .unwrap()
3200            .0
3201            .read_with(cx, |buffer, _| buffer.text()),
3202        "const THREE: usize = 1;"
3203    );
3204    assert_eq!(
3205        transaction
3206            .into_keys()
3207            .next()
3208            .unwrap()
3209            .read_with(cx, |buffer, _| buffer.text()),
3210        "const TWO: usize = one::THREE + one::THREE;"
3211    );
3212}
3213
3214#[gpui::test]
3215async fn test_search(cx: &mut gpui::TestAppContext) {
3216    let fs = FakeFs::new(cx.background());
3217    fs.insert_tree(
3218        "/dir",
3219        json!({
3220            "one.rs": "const ONE: usize = 1;",
3221            "two.rs": "const TWO: usize = one::ONE + one::ONE;",
3222            "three.rs": "const THREE: usize = one::ONE + two::TWO;",
3223            "four.rs": "const FOUR: usize = one::ONE + three::THREE;",
3224        }),
3225    )
3226    .await;
3227    let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
3228    assert_eq!(
3229        search(&project, SearchQuery::text("TWO", false, true), cx)
3230            .await
3231            .unwrap(),
3232        HashMap::from_iter([
3233            ("two.rs".to_string(), vec![6..9]),
3234            ("three.rs".to_string(), vec![37..40])
3235        ])
3236    );
3237
3238    let buffer_4 = project
3239        .update(cx, |project, cx| {
3240            project.open_local_buffer("/dir/four.rs", cx)
3241        })
3242        .await
3243        .unwrap();
3244    buffer_4.update(cx, |buffer, cx| {
3245        let text = "two::TWO";
3246        buffer.edit([(20..28, text), (31..43, text)], None, cx);
3247    });
3248
3249    assert_eq!(
3250        search(&project, SearchQuery::text("TWO", false, true), cx)
3251            .await
3252            .unwrap(),
3253        HashMap::from_iter([
3254            ("two.rs".to_string(), vec![6..9]),
3255            ("three.rs".to_string(), vec![37..40]),
3256            ("four.rs".to_string(), vec![25..28, 36..39])
3257        ])
3258    );
3259
3260    async fn search(
3261        project: &ModelHandle<Project>,
3262        query: SearchQuery,
3263        cx: &mut gpui::TestAppContext,
3264    ) -> Result<HashMap<String, Vec<Range<usize>>>> {
3265        let results = project
3266            .update(cx, |project, cx| project.search(query, cx))
3267            .await?;
3268
3269        Ok(results
3270            .into_iter()
3271            .map(|(buffer, ranges)| {
3272                buffer.read_with(cx, |buffer, _| {
3273                    let path = buffer.file().unwrap().path().to_string_lossy().to_string();
3274                    let ranges = ranges
3275                        .into_iter()
3276                        .map(|range| range.to_offset(buffer))
3277                        .collect::<Vec<_>>();
3278                    (path, ranges)
3279                })
3280            })
3281            .collect())
3282    }
3283}