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