diff --git a/crates/tui/src/tui/file_mention.rs b/crates/tui/src/tui/file_mention.rs index 86d237c22..4e9e2d368 100644 --- a/crates/tui/src/tui/file_mention.rs +++ b/crates/tui/src/tui/file_mention.rs @@ -270,7 +270,10 @@ pub fn try_autocomplete_file_mention(app: &mut App) -> bool { let ws = workspace_for_app(app); let candidates = find_file_mention_completions(&ws, &partial, FILE_MENTION_COMPLETION_LIMIT); if candidates.is_empty() { - app.status_message = Some(format!("No files match @{partial}")); + app.status_message = Some(no_file_mention_matches_status( + &partial, + app.mention_walk_depth, + )); return true; } if candidates.len() == 1 { @@ -297,6 +300,27 @@ pub fn try_autocomplete_file_mention(app: &mut App) -> bool { true } +fn no_file_mention_matches_status(partial: &str, walk_depth: usize) -> String { + if path_partial_reaches_walk_depth(partial, walk_depth) { + format!( + "No files match @{partial} (mention_walk_depth={walk_depth}; use /config set mention_walk_depth 0 to search deeper)" + ) + } else { + format!("No files match @{partial}") + } +} + +fn path_partial_reaches_walk_depth(partial: &str, walk_depth: usize) -> bool { + if walk_depth == 0 { + return false; + } + let component_count = partial + .split(['/', '\\']) + .filter(|component| !component.is_empty()) + .count(); + component_count >= walk_depth +} + /// Splice a completion into the input, replacing the `@` token at /// `byte_start` with `@`. Cursor moves to the end of the new /// token so further keystrokes extend (or escape via space) naturally. diff --git a/crates/tui/src/tui/ui/tests.rs b/crates/tui/src/tui/ui/tests.rs index 166031371..a1420dbb8 100644 --- a/crates/tui/src/tui/ui/tests.rs +++ b/crates/tui/src/tui/ui/tests.rs @@ -4694,6 +4694,59 @@ fn try_autocomplete_file_mention_no_match_reports_status() { ); } +#[test] +fn try_autocomplete_file_mention_no_match_mentions_depth_cap_for_path_like_partial() { + let tmpdir = TempDir::new().expect("tempdir"); + + let mut app = create_test_app(); + app.workspace = tmpdir.path().to_path_buf(); + app.mention_walk_depth = 6; + app.input = "@a/b/c/d/e/f/g/target".to_string(); + app.cursor_position = app.input.chars().count(); + + assert!(try_autocomplete_file_mention(&mut app)); + assert_eq!( + app.status_message.as_deref(), + Some( + "No files match @a/b/c/d/e/f/g/target (mention_walk_depth=6; use /config set mention_walk_depth 0 to search deeper)" + ) + ); +} + +#[test] +fn try_autocomplete_file_mention_no_match_skips_depth_hint_for_shallow_path() { + let tmpdir = TempDir::new().expect("tempdir"); + + let mut app = create_test_app(); + app.workspace = tmpdir.path().to_path_buf(); + app.mention_walk_depth = 6; + app.input = "@shallow_missing/main.rs".to_string(); + app.cursor_position = app.input.chars().count(); + + assert!(try_autocomplete_file_mention(&mut app)); + assert_eq!( + app.status_message.as_deref(), + Some("No files match @shallow_missing/main.rs") + ); +} + +#[test] +fn try_autocomplete_file_mention_no_match_skips_depth_hint_when_unlimited() { + let tmpdir = TempDir::new().expect("tempdir"); + + let mut app = create_test_app(); + app.workspace = tmpdir.path().to_path_buf(); + app.mention_walk_depth = 0; + app.input = "@a/b/c/d/e/f/g/target".to_string(); + app.cursor_position = app.input.chars().count(); + + assert!(try_autocomplete_file_mention(&mut app)); + assert_eq!( + app.status_message.as_deref(), + Some("No files match @a/b/c/d/e/f/g/target") + ); +} + #[test] fn try_autocomplete_file_mention_returns_false_outside_mention() { let mut app = create_test_app();