2659 lines · 77.7 KB · Rust
RawDownload
use super::*;use crate::language_settings::{    AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent,};use crate::Buffer;use clock::ReplicaId;use collections::BTreeMap;use futures::FutureExt as _;use gpui::{AppContext, Model};use gpui::{Context, TestAppContext};use indoc::indoc;use proto::deserialize_operation;use rand::prelude::*;use regex::RegexBuilder;use settings::SettingsStore;use std::{    env,    ops::Range,    time::{Duration, Instant},};use text::network::Network;use text::{BufferId, LineEnding};use text::{Point, ToPoint};use unindent::Unindent as _;use util::{assert_set_eq, post_inc, test::marked_text_ranges, RandomCharIter}; lazy_static! {    static ref TRAILING_WHITESPACE_REGEX: Regex = RegexBuilder::new("[ \t]+$")        .multi_line(true)        .build()        .unwrap();} #[cfg(test)]#[ctor::ctor]fn init_logger() {    if std::env::var("RUST_LOG").is_ok() {        env_logger::init();    }} #[gpui::test]fn test_line_endings(cx: &mut gpui::AppContext) {    init_settings(cx, |_| {});     cx.new_model(|cx| {        let mut buffer = Buffer::new(            0,            BufferId::new(cx.entity_id().as_u64()).unwrap(),            "one\r\ntwo\rthree",        )        .with_language(Arc::new(rust_lang()), cx);        assert_eq!(buffer.text(), "one\ntwo\nthree");        assert_eq!(buffer.line_ending(), LineEnding::Windows);         buffer.check_invariants();        buffer.edit(            [(buffer.len()..buffer.len(), "\r\nfour")],            Some(AutoindentMode::EachLine),            cx,        );        buffer.edit([(0..0, "zero\r\n")], None, cx);        assert_eq!(buffer.text(), "zero\none\ntwo\nthree\nfour");        assert_eq!(buffer.line_ending(), LineEnding::Windows);        buffer.check_invariants();         buffer    });} #[gpui::test]fn test_select_language(cx: &mut AppContext) {    init_settings(cx, |_| {});     let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));    registry.add(Arc::new(Language::new(        LanguageConfig {            name: "Rust".into(),            matcher: LanguageMatcher {                path_suffixes: vec!["rs".to_string()],                ..Default::default()            },            ..Default::default()        },        Some(tree_sitter_rust::language()),    )));    registry.add(Arc::new(Language::new(        LanguageConfig {            name: "Make".into(),            matcher: LanguageMatcher {                path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],                ..Default::default()            },            ..Default::default()        },        Some(tree_sitter_rust::language()),    )));     // matching file extension    assert_eq!(        registry            .language_for_file(&file("src/lib.rs"), None, cx)            .now_or_never()            .and_then(|l| Some(l.ok()?.name())),        Some("Rust".into())    );    assert_eq!(        registry            .language_for_file(&file("src/lib.mk"), None, cx)            .now_or_never()            .and_then(|l| Some(l.ok()?.name())),        Some("Make".into())    );     // matching filename    assert_eq!(        registry            .language_for_file(&file("src/Makefile"), None, cx)            .now_or_never()            .and_then(|l| Some(l.ok()?.name())),        Some("Make".into())    );     // matching suffix that is not the full file extension or filename    assert_eq!(        registry            .language_for_file(&file("zed/cars"), None, cx)            .now_or_never()            .and_then(|l| Some(l.ok()?.name())),        None    );    assert_eq!(        registry            .language_for_file(&file("zed/a.cars"), None, cx)            .now_or_never()            .and_then(|l| Some(l.ok()?.name())),        None    );    assert_eq!(        registry            .language_for_file(&file("zed/sumk"), None, cx)            .now_or_never()            .and_then(|l| Some(l.ok()?.name())),        None    );} #[gpui::test(iterations = 10)]async fn test_first_line_pattern(cx: &mut TestAppContext) {    cx.update(|cx| init_settings(cx, |_| {}));     let languages = LanguageRegistry::test(cx.executor());    let languages = Arc::new(languages);     languages.register_test_language(LanguageConfig {        name: "JavaScript".into(),        matcher: LanguageMatcher {            path_suffixes: vec!["js".into()],            first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()),        },        ..Default::default()    });     cx.read(|cx| languages.language_for_file(&file("the/script"), None, cx))        .await        .unwrap_err();    cx.read(|cx| languages.language_for_file(&file("the/script"), Some(&"nothing".into()), cx))        .await        .unwrap_err();    assert_eq!(        cx.read(|cx| languages.language_for_file(            &file("the/script"),            Some(&"#!/bin/env node".into()),            cx        ))        .await        .unwrap()        .name()        .as_ref(),        "JavaScript"    );} #[gpui::test]async fn test_language_for_file_with_custom_file_types(cx: &mut TestAppContext) {    cx.update(|cx| {        init_settings(cx, |settings| {            settings.file_types.extend([                ("TypeScript".into(), vec!["js".into()]),                ("C++".into(), vec!["c".into()]),            ]);        })    });     let languages = Arc::new(LanguageRegistry::test(cx.executor()));     for config in [        LanguageConfig {            name: "JavaScript".into(),            matcher: LanguageMatcher {                path_suffixes: vec!["js".to_string()],                ..Default::default()            },            ..Default::default()        },        LanguageConfig {            name: "TypeScript".into(),            matcher: LanguageMatcher {                path_suffixes: vec!["js".to_string()],                ..Default::default()            },            ..Default::default()        },        LanguageConfig {            name: "C++".into(),            matcher: LanguageMatcher {                path_suffixes: vec!["cpp".to_string()],                ..Default::default()            },            ..Default::default()        },        LanguageConfig {            name: "C".into(),            matcher: LanguageMatcher {                path_suffixes: vec!["c".to_string()],                ..Default::default()            },            ..Default::default()        },    ] {        languages.add(Arc::new(Language::new(config, None)));    }     let language = cx        .read(|cx| languages.language_for_file(&file("foo.js"), None, cx))        .await        .unwrap();    assert_eq!(language.name().as_ref(), "TypeScript");    let language = cx        .read(|cx| languages.language_for_file(&file("foo.c"), None, cx))        .await        .unwrap();    assert_eq!(language.name().as_ref(), "C++");} fn file(path: &str) -> Arc<dyn File> {    Arc::new(TestFile {        path: Path::new(path).into(),        root_name: "zed".into(),    })} #[gpui::test]fn test_edit_events(cx: &mut gpui::AppContext) {    let mut now = Instant::now();    let buffer_1_events = Arc::new(Mutex::new(Vec::new()));    let buffer_2_events = Arc::new(Mutex::new(Vec::new()));     let buffer1 = cx        .new_model(|cx| Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "abcdef"));    let buffer2 = cx        .new_model(|cx| Buffer::new(1, BufferId::new(cx.entity_id().as_u64()).unwrap(), "abcdef"));    let buffer1_ops = Arc::new(Mutex::new(Vec::new()));    buffer1.update(cx, {        let buffer1_ops = buffer1_ops.clone();        |buffer, cx| {            let buffer_1_events = buffer_1_events.clone();            cx.subscribe(&buffer1, move |_, _, event, _| match event.clone() {                Event::Operation(op) => buffer1_ops.lock().push(op),                event => buffer_1_events.lock().push(event),            })            .detach();            let buffer_2_events = buffer_2_events.clone();            cx.subscribe(&buffer2, move |_, _, event, _| {                buffer_2_events.lock().push(event.clone())            })            .detach();             // An edit emits an edited event, followed by a dirty changed event,            // since the buffer was previously in a clean state.            buffer.edit([(2..4, "XYZ")], None, cx);             // An empty transaction does not emit any events.            buffer.start_transaction();            buffer.end_transaction(cx);             // A transaction containing two edits emits one edited event.            now += Duration::from_secs(1);            buffer.start_transaction_at(now);            buffer.edit([(5..5, "u")], None, cx);            buffer.edit([(6..6, "w")], None, cx);            buffer.end_transaction_at(now, cx);             // Undoing a transaction emits one edited event.            buffer.undo(cx);        }    });     // Incorporating a set of remote ops emits a single edited event,    // followed by a dirty changed event.    buffer2.update(cx, |buffer, cx| {        buffer.apply_ops(buffer1_ops.lock().drain(..), cx).unwrap();    });    assert_eq!(        mem::take(&mut *buffer_1_events.lock()),        vec![            Event::Edited,            Event::DirtyChanged,            Event::Edited,            Event::Edited,        ]    );    assert_eq!(        mem::take(&mut *buffer_2_events.lock()),        vec![Event::Edited, Event::DirtyChanged]    );     buffer1.update(cx, |buffer, cx| {        // Undoing the first transaction emits edited event, followed by a        // dirty changed event, since the buffer is again in a clean state.        buffer.undo(cx);    });    // Incorporating the remote ops again emits a single edited event,    // followed by a dirty changed event.    buffer2.update(cx, |buffer, cx| {        buffer.apply_ops(buffer1_ops.lock().drain(..), cx).unwrap();    });    assert_eq!(        mem::take(&mut *buffer_1_events.lock()),        vec![Event::Edited, Event::DirtyChanged,]    );    assert_eq!(        mem::take(&mut *buffer_2_events.lock()),        vec![Event::Edited, Event::DirtyChanged]    );} #[gpui::test]async fn test_apply_diff(cx: &mut TestAppContext) {    let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";    let buffer =        cx.new_model(|cx| Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text));    let anchor = buffer.update(cx, |buffer, _| buffer.anchor_before(Point::new(3, 3)));     let text = "a\nccc\ndddd\nffffff\n";    let diff = buffer.update(cx, |b, cx| b.diff(text.into(), cx)).await;    buffer.update(cx, |buffer, cx| {        buffer.apply_diff(diff, cx).unwrap();        assert_eq!(buffer.text(), text);        assert_eq!(anchor.to_point(buffer), Point::new(2, 3));    });     let text = "a\n1\n\nccc\ndd2dd\nffffff\n";    let diff = buffer.update(cx, |b, cx| b.diff(text.into(), cx)).await;    buffer.update(cx, |buffer, cx| {        buffer.apply_diff(diff, cx).unwrap();        assert_eq!(buffer.text(), text);        assert_eq!(anchor.to_point(buffer), Point::new(4, 4));    });} #[gpui::test(iterations = 10)]async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {    let text = [        "zero",     //        "one  ",    // 2 trailing spaces        "two",      //        "three   ", // 3 trailing spaces        "four",     //        "five    ", // 4 trailing spaces    ]    .join("\n");     let buffer =        cx.new_model(|cx| Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text));     // Spawn a task to format the buffer's whitespace.    // Pause so that the foratting task starts running.    let format = buffer.update(cx, |buffer, cx| buffer.remove_trailing_whitespace(cx));    smol::future::yield_now().await;     // Edit the buffer while the normalization task is running.    let version_before_edit = buffer.update(cx, |buffer, _| buffer.version());    buffer.update(cx, |buffer, cx| {        buffer.edit(            [                (Point::new(0, 1)..Point::new(0, 1), "EE"),                (Point::new(3, 5)..Point::new(3, 5), "EEE"),            ],            None,            cx,        );    });     let format_diff = format.await;    buffer.update(cx, |buffer, cx| {        let version_before_format = format_diff.base_version.clone();        buffer.apply_diff(format_diff, cx);         // The outcome depends on the order of concurrent tasks.        //        // If the edit occurred while searching for trailing whitespace ranges,        // then the trailing whitespace region touched by the edit is left intact.        if version_before_format == version_before_edit {            assert_eq!(                buffer.text(),                [                    "zEEero",      //                    "one",         //                    "two",         //                    "threeEEE   ", //                    "four",        //                    "five",        //                ]                .join("\n")            );        }        // Otherwise, all trailing whitespace is removed.        else {            assert_eq!(                buffer.text(),                [                    "zEEero",   //                    "one",      //                    "two",      //                    "threeEEE", //                    "four",     //                    "five",     //                ]                .join("\n")            );        }    });} #[gpui::test]async fn test_reparse(cx: &mut gpui::TestAppContext) {    let text = "fn a() {}";    let buffer = cx.new_model(|cx| {        Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)            .with_language(Arc::new(rust_lang()), cx)    });     // Wait for the initial text to parse    cx.executor().run_until_parked();    assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing()));    assert_eq!(        get_tree_sexp(&buffer, cx),        concat!(            "(source_file (function_item name: (identifier) ",            "parameters: (parameters) ",            "body: (block)))"        )    );     buffer.update(cx, |buffer, _| {        buffer.set_sync_parse_timeout(Duration::ZERO)    });     // Perform some edits (add parameter and variable reference)    // Parsing doesn't begin until the transaction is complete    buffer.update(cx, |buf, cx| {        buf.start_transaction();         let offset = buf.text().find(')').unwrap();        buf.edit([(offset..offset, "b: C")], None, cx);        assert!(!buf.is_parsing());         let offset = buf.text().find('}').unwrap();        buf.edit([(offset..offset, " d; ")], None, cx);        assert!(!buf.is_parsing());         buf.end_transaction(cx);        assert_eq!(buf.text(), "fn a(b: C) { d; }");        assert!(buf.is_parsing());    });    cx.executor().run_until_parked();    assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing()));    assert_eq!(        get_tree_sexp(&buffer, cx),        concat!(            "(source_file (function_item name: (identifier) ",            "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",            "body: (block (expression_statement (identifier)))))"        )    );     // Perform a series of edits without waiting for the current parse to complete:    // * turn identifier into a field expression    // * turn field expression into a method call    // * add a turbofish to the method call    buffer.update(cx, |buf, cx| {        let offset = buf.text().find(';').unwrap();        buf.edit([(offset..offset, ".e")], None, cx);        assert_eq!(buf.text(), "fn a(b: C) { d.e; }");        assert!(buf.is_parsing());    });    buffer.update(cx, |buf, cx| {        let offset = buf.text().find(';').unwrap();        buf.edit([(offset..offset, "(f)")], None, cx);

Lines 1–500 of 2659.