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