2371 lines · 77.3 KB · Rust
RawDownload
use std::{assert_eq, future::IntoFuture, path::Path, time::Duration}; use super::*;use editor::Editor;use gpui::{Entity, TestAppContext, VisualTestContext};use menu::{Confirm, SelectNext, SelectPrev};use project::{RemoveOptions, FS_WATCH_LATENCY};use serde_json::json;use util::path;use workspace::{AppState, ToggleFileFinder, Workspace}; #[ctor::ctor]fn init_logger() {    if std::env::var("RUST_LOG").is_ok() {        env_logger::init();    }} #[test]fn test_path_elision() {    #[track_caller]    fn check(path: &str, budget: usize, matches: impl IntoIterator<Item = usize>, expected: &str) {        let mut path = path.to_owned();        let slice = PathComponentSlice::new(&path);        let matches = Vec::from_iter(matches);        if let Some(range) = slice.elision_range(budget - 1, &matches) {            path.replace_range(range, "…");        }        assert_eq!(path, expected);    }     // Simple cases, mostly to check that different path shapes are handled gracefully.    check("p/a/b/c/d/", 6, [], "p/…/d/");    check("p/a/b/c/d/", 1, [2, 4, 6], "p/a/b/c/d/");    check("p/a/b/c/d/", 10, [2, 6], "p/a/…/c/d/");    check("p/a/b/c/d/", 8, [6], "p/…/c/d/");     check("p/a/b/c/d", 5, [], "p/…/d");    check("p/a/b/c/d", 9, [2, 4, 6], "p/a/b/c/d");    check("p/a/b/c/d", 9, [2, 6], "p/a/…/c/d");    check("p/a/b/c/d", 7, [6], "p/…/c/d");     check("/p/a/b/c/d/", 7, [], "/p/…/d/");    check("/p/a/b/c/d/", 11, [3, 5, 7], "/p/a/b/c/d/");    check("/p/a/b/c/d/", 11, [3, 7], "/p/a/…/c/d/");    check("/p/a/b/c/d/", 9, [7], "/p/…/c/d/");     // If the budget can't be met, no elision is done.    check(        "project/dir/child/grandchild",        5,        [],        "project/dir/child/grandchild",    );     // The longest unmatched segment is picked for elision.    check(        "project/one/two/X/three/sub",        21,        [16],        "project/…/X/three/sub",    );     // Elision stops when the budget is met, even though there are more components in the chosen segment.    // It proceeds from the end of the unmatched segment that is closer to the midpoint of the path.    check(        "project/one/two/three/X/sub",        21,        [22],        "project/…/three/X/sub",    )} #[test]fn test_custom_project_search_ordering_in_file_finder() {    let mut file_finder_sorted_output = vec![        ProjectPanelOrdMatch(PathMatch {            score: 0.5,            positions: Vec::new(),            worktree_id: 0,            path: Arc::from(Path::new("b0.5")),            path_prefix: Arc::default(),            distance_to_relative_ancestor: 0,            is_dir: false,        }),        ProjectPanelOrdMatch(PathMatch {            score: 1.0,            positions: Vec::new(),            worktree_id: 0,            path: Arc::from(Path::new("c1.0")),            path_prefix: Arc::default(),            distance_to_relative_ancestor: 0,            is_dir: false,        }),        ProjectPanelOrdMatch(PathMatch {            score: 1.0,            positions: Vec::new(),            worktree_id: 0,            path: Arc::from(Path::new("a1.0")),            path_prefix: Arc::default(),            distance_to_relative_ancestor: 0,            is_dir: false,        }),        ProjectPanelOrdMatch(PathMatch {            score: 0.5,            positions: Vec::new(),            worktree_id: 0,            path: Arc::from(Path::new("a0.5")),            path_prefix: Arc::default(),            distance_to_relative_ancestor: 0,            is_dir: false,        }),        ProjectPanelOrdMatch(PathMatch {            score: 1.0,            positions: Vec::new(),            worktree_id: 0,            path: Arc::from(Path::new("b1.0")),            path_prefix: Arc::default(),            distance_to_relative_ancestor: 0,            is_dir: false,        }),    ];    file_finder_sorted_output.sort_by(|a, b| b.cmp(a));     assert_eq!(        file_finder_sorted_output,        vec![            ProjectPanelOrdMatch(PathMatch {                score: 1.0,                positions: Vec::new(),                worktree_id: 0,                path: Arc::from(Path::new("a1.0")),                path_prefix: Arc::default(),                distance_to_relative_ancestor: 0,                is_dir: false,            }),            ProjectPanelOrdMatch(PathMatch {                score: 1.0,                positions: Vec::new(),                worktree_id: 0,                path: Arc::from(Path::new("b1.0")),                path_prefix: Arc::default(),                distance_to_relative_ancestor: 0,                is_dir: false,            }),            ProjectPanelOrdMatch(PathMatch {                score: 1.0,                positions: Vec::new(),                worktree_id: 0,                path: Arc::from(Path::new("c1.0")),                path_prefix: Arc::default(),                distance_to_relative_ancestor: 0,                is_dir: false,            }),            ProjectPanelOrdMatch(PathMatch {                score: 0.5,                positions: Vec::new(),                worktree_id: 0,                path: Arc::from(Path::new("a0.5")),                path_prefix: Arc::default(),                distance_to_relative_ancestor: 0,                is_dir: false,            }),            ProjectPanelOrdMatch(PathMatch {                score: 0.5,                positions: Vec::new(),                worktree_id: 0,                path: Arc::from(Path::new("b0.5")),                path_prefix: Arc::default(),                distance_to_relative_ancestor: 0,                is_dir: false,            }),        ]    );} #[gpui::test]async fn test_matching_paths(cx: &mut TestAppContext) {    let app_state = init_test(cx);    app_state        .fs        .as_fake()        .insert_tree(            path!("/root"),            json!({                "a": {                    "banana": "",                    "bandana": "",                }            }),        )        .await;     let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;     let (picker, workspace, cx) = build_find_picker(project, cx);     cx.simulate_input("bna");    picker.update(cx, |picker, _| {        assert_eq!(picker.delegate.matches.len(), 2);    });    cx.dispatch_action(SelectNext);    cx.dispatch_action(Confirm);    cx.read(|cx| {        let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();        assert_eq!(active_editor.read(cx).title(cx), "bandana");    });     for bandana_query in [        "bandana",        " bandana",        "bandana ",        " bandana ",        " ndan ",        " band ",        "a bandana",    ] {        picker            .update_in(cx, |picker, window, cx| {                picker                    .delegate                    .update_matches(bandana_query.to_string(), window, cx)            })            .await;        picker.update(cx, |picker, _| {            assert_eq!(                picker.delegate.matches.len(),                1,                "Wrong number of matches for bandana query '{bandana_query}'"            );        });        cx.dispatch_action(SelectNext);        cx.dispatch_action(Confirm);        cx.read(|cx| {            let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();            assert_eq!(                active_editor.read(cx).title(cx),                "bandana",                "Wrong match for bandana query '{bandana_query}'"            );        });    }} #[gpui::test]async fn test_absolute_paths(cx: &mut TestAppContext) {    let app_state = init_test(cx);    app_state        .fs        .as_fake()        .insert_tree(            path!("/root"),            json!({                "a": {                    "file1.txt": "",                    "b": {                        "file2.txt": "",                    },                }            }),        )        .await;     let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;     let (picker, workspace, cx) = build_find_picker(project, cx);     let matching_abs_path = path!("/root/a/b/file2.txt").to_string();    picker        .update_in(cx, |picker, window, cx| {            picker                .delegate                .update_matches(matching_abs_path, window, cx)        })        .await;    picker.update(cx, |picker, _| {        assert_eq!(            collect_search_matches(picker).search_paths_only(),            vec![PathBuf::from("a/b/file2.txt")],            "Matching abs path should be the only match"        )    });    cx.dispatch_action(SelectNext);    cx.dispatch_action(Confirm);    cx.read(|cx| {        let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();        assert_eq!(active_editor.read(cx).title(cx), "file2.txt");    });     let mismatching_abs_path = path!("/root/a/b/file1.txt").to_string();    picker        .update_in(cx, |picker, window, cx| {            picker                .delegate                .update_matches(mismatching_abs_path, window, cx)        })        .await;    picker.update(cx, |picker, _| {        assert_eq!(            collect_search_matches(picker).search_paths_only(),            Vec::<PathBuf>::new(),            "Mismatching abs path should produce no matches"        )    });} #[gpui::test]async fn test_complex_path(cx: &mut TestAppContext) {    let app_state = init_test(cx);    app_state        .fs        .as_fake()        .insert_tree(            path!("/root"),            json!({                "其他": {                    "S数据表格": {                        "task.xlsx": "some content",                    },                }            }),        )        .await;     let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;     let (picker, workspace, cx) = build_find_picker(project, cx);     cx.simulate_input("t");    picker.update(cx, |picker, _| {        assert_eq!(picker.delegate.matches.len(), 1);        assert_eq!(            collect_search_matches(picker).search_paths_only(),            vec![PathBuf::from("其他/S数据表格/task.xlsx")],        )    });    cx.dispatch_action(SelectNext);    cx.dispatch_action(Confirm);    cx.read(|cx| {        let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();        assert_eq!(active_editor.read(cx).title(cx), "task.xlsx");    });} #[gpui::test]async fn test_row_column_numbers_query_inside_file(cx: &mut TestAppContext) {    let app_state = init_test(cx);     let first_file_name = "first.rs";    let first_file_contents = "// First Rust file";    app_state        .fs        .as_fake()        .insert_tree(            path!("/src"),            json!({                "test": {                    first_file_name: first_file_contents,                    "second.rs": "// Second Rust file",                }            }),        )        .await;     let project = Project::test(app_state.fs.clone(), [path!("/src").as_ref()], cx).await;     let (picker, workspace, cx) = build_find_picker(project, cx);     let file_query = &first_file_name[..3];    let file_row = 1;    let file_column = 3;    assert!(file_column <= first_file_contents.len());    let query_inside_file = format!("{file_query}:{file_row}:{file_column}");    picker        .update_in(cx, |finder, window, cx| {            finder                .delegate                .update_matches(query_inside_file.to_string(), window, cx)        })        .await;    picker.update(cx, |finder, _| {        let finder = &finder.delegate;        assert_eq!(finder.matches.len(), 1);        let latest_search_query = finder            .latest_search_query            .as_ref()            .expect("Finder should have a query after the update_matches call");        assert_eq!(latest_search_query.raw_query, query_inside_file);        assert_eq!(latest_search_query.file_query_end, Some(file_query.len()));        assert_eq!(latest_search_query.path_position.row, Some(file_row));        assert_eq!(            latest_search_query.path_position.column,            Some(file_column as u32)        );    });     cx.dispatch_action(SelectNext);    cx.dispatch_action(Confirm);     let editor = cx.update(|_, cx| workspace.read(cx).active_item_as::<Editor>(cx).unwrap());    cx.executor().advance_clock(Duration::from_secs(2));     editor.update(cx, |editor, cx| {            let all_selections = editor.selections.all_adjusted(cx);            assert_eq!(                all_selections.len(),                1,                "Expected to have 1 selection (caret) after file finder confirm, but got: {all_selections:?}"            );            let caret_selection = all_selections.into_iter().next().unwrap();            assert_eq!(caret_selection.start, caret_selection.end,                "Caret selection should have its start and end at the same position");            assert_eq!(file_row, caret_selection.start.row + 1,                "Query inside file should get caret with the same focus row");            assert_eq!(file_column, caret_selection.start.column as usize + 1,                "Query inside file should get caret with the same focus column");        });} #[gpui::test]async fn test_row_column_numbers_query_outside_file(cx: &mut TestAppContext) {    let app_state = init_test(cx);     let first_file_name = "first.rs";    let first_file_contents = "// First Rust file";    app_state        .fs        .as_fake()        .insert_tree(            path!("/src"),            json!({                "test": {                    first_file_name: first_file_contents,                    "second.rs": "// Second Rust file",                }            }),        )        .await;     let project = Project::test(app_state.fs.clone(), [path!("/src").as_ref()], cx).await;     let (picker, workspace, cx) = build_find_picker(project, cx);     let file_query = &first_file_name[..3];    let file_row = 200;    let file_column = 300;    assert!(file_column > first_file_contents.len());    let query_outside_file = format!("{file_query}:{file_row}:{file_column}");    picker        .update_in(cx, |picker, window, cx| {            picker                .delegate                .update_matches(query_outside_file.to_string(), window, cx)        })        .await;    picker.update(cx, |finder, _| {        let delegate = &finder.delegate;        assert_eq!(delegate.matches.len(), 1);        let latest_search_query = delegate            .latest_search_query            .as_ref()            .expect("Finder should have a query after the update_matches call");        assert_eq!(latest_search_query.raw_query, query_outside_file);        assert_eq!(latest_search_query.file_query_end, Some(file_query.len()));        assert_eq!(latest_search_query.path_position.row, Some(file_row));        assert_eq!(            latest_search_query.path_position.column,            Some(file_column as u32)        );    });     cx.dispatch_action(SelectNext);    cx.dispatch_action(Confirm);     let editor = cx.update(|_, cx| workspace.read(cx).active_item_as::<Editor>(cx).unwrap());    cx.executor().advance_clock(Duration::from_secs(2));     editor.update(cx, |editor, cx| {            let all_selections = editor.selections.all_adjusted(cx);            assert_eq!(                all_selections.len(),                1,                "Expected to have 1 selection (caret) after file finder confirm, but got: {all_selections:?}"            );            let caret_selection = all_selections.into_iter().next().unwrap();            assert_eq!(caret_selection.start, caret_selection.end,                "Caret selection should have its start and end at the same position");            assert_eq!(0, caret_selection.start.row,                "Excessive rows (as in query outside file borders) should get trimmed to last file row");            assert_eq!(first_file_contents.len(), caret_selection.start.column as usize,                "Excessive columns (as in query outside file borders) should get trimmed to selected row's last column");        });} #[gpui::test]async fn test_matching_cancellation(cx: &mut TestAppContext) {    let app_state = init_test(cx);    app_state        .fs        .as_fake()

Lines 1–500 of 2371.