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. Load more
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()