project_tests.rs

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