diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index 86c17625e1..3f590273df 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -713,17 +713,42 @@ pub(crate) async fn find_file( cx: &mut AsyncWindowContext, ) -> Option<(Range, ResolvedPath)> { let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot()).ok()?; - + let scope = snapshot.language_scope_at(position); let (range, candidate_file_path) = surrounding_filename(snapshot, position)?; - let existing_path = project - .update(cx, |project, cx| { - project.resolve_existing_file_path(&candidate_file_path, buffer, cx) - }) - .ok()? - .await?; + async fn check_path( + candidate_file_path: &str, + project: &Model, + buffer: &Model, + cx: &mut AsyncWindowContext, + ) -> Option { + project + .update(cx, |project, cx| { + project.resolve_existing_file_path(&candidate_file_path, buffer, cx) + }) + .ok()? + .await + } - Some((range, existing_path)) + if let Some(existing_path) = check_path(&candidate_file_path, &project, buffer, cx).await { + return Some((range, existing_path)); + } + + if let Some(scope) = scope { + for suffix in scope.path_suffixes() { + if candidate_file_path.ends_with(format!(".{suffix}").as_str()) { + continue; + } + + let suffixed_candidate = format!("{candidate_file_path}.{suffix}"); + if let Some(existing_path) = check_path(&suffixed_candidate, &project, buffer, cx).await + { + return Some((range, existing_path)); + } + } + } + + None } fn surrounding_filename( @@ -1490,7 +1515,8 @@ mod tests { You can't go to a file that does_not_exist.txt. Go to file2.rs if you want. Or go to ../dir/file2.rs if you want. - Or go to /root/dir/file2.rs if project is local.ˇ + Or go to /root/dir/file2.rs if project is local. + Or go to /root/dir/file2 if this is a Rust file.ˇ "}); // File does not exist @@ -1499,6 +1525,7 @@ mod tests { Go to file2.rs if you want. Or go to ../dir/file2.rs if you want. Or go to /root/dir/file2.rs if project is local. + Or go to /root/dir/file2 if this is a Rust file. "}); cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key()); // No highlight @@ -1517,6 +1544,7 @@ mod tests { Go to fˇile2.rs if you want. Or go to ../dir/file2.rs if you want. Or go to /root/dir/file2.rs if project is local. + Or go to /root/dir/file2 if this is a Rust file. "}); cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key()); @@ -1525,6 +1553,7 @@ mod tests { Go to «file2.rsˇ» if you want. Or go to ../dir/file2.rs if you want. Or go to /root/dir/file2.rs if project is local. + Or go to /root/dir/file2 if this is a Rust file. "}); // Moving the mouse over a relative path that does exist should highlight it @@ -1533,6 +1562,7 @@ mod tests { Go to file2.rs if you want. Or go to ../dir/fˇile2.rs if you want. Or go to /root/dir/file2.rs if project is local. + Or go to /root/dir/file2 if this is a Rust file. "}); cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key()); @@ -1541,6 +1571,7 @@ mod tests { Go to file2.rs if you want. Or go to «../dir/file2.rsˇ» if you want. Or go to /root/dir/file2.rs if project is local. + Or go to /root/dir/file2 if this is a Rust file. "}); // Moving the mouse over an absolute path that does exist should highlight it @@ -1549,6 +1580,7 @@ mod tests { Go to file2.rs if you want. Or go to ../dir/file2.rs if you want. Or go to /root/diˇr/file2.rs if project is local. + Or go to /root/dir/file2 if this is a Rust file. "}); cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key()); @@ -1557,6 +1589,25 @@ mod tests { Go to file2.rs if you want. Or go to ../dir/file2.rs if you want. Or go to «/root/dir/file2.rsˇ» if project is local. + Or go to /root/dir/file2 if this is a Rust file. + "}); + + // Moving the mouse over a path that exists, if we add the language-specific suffix, it should highlight it + let screen_coord = cx.pixel_position(indoc! {" + You can't go to a file that does_not_exist.txt. + Go to file2.rs if you want. + Or go to ../dir/file2.rs if you want. + Or go to /root/dir/file2.rs if project is local. + Or go to /root/diˇr/file2 if this is a Rust file. + "}); + + cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key()); + cx.assert_editor_text_highlights::(indoc! {" + You can't go to a file that does_not_exist.txt. + Go to file2.rs if you want. + Or go to ../dir/file2.rs if you want. + Or go to /root/dir/file2.rs if project is local. + Or go to «/root/dir/file2ˇ» if this is a Rust file. "}); cx.simulate_click(screen_coord, Modifiers::secondary_key()); diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index cd39490d0b..3112d88aa5 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -1410,6 +1410,10 @@ impl Language { } impl LanguageScope { + pub fn path_suffixes(&self) -> &[String] { + &self.language.path_suffixes() + } + pub fn language_name(&self) -> LanguageName { self.language.config.name.clone() } diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index 06c5f0bd3f..67a674afa6 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -969,6 +969,9 @@ mod test { fs.as_fake() .insert_file("/root/dir/file2.rs", "This is file2.rs".as_bytes().to_vec()) .await; + fs.as_fake() + .insert_file("/root/dir/file3.rs", "go to file3".as_bytes().to_vec()) + .await; // Put the path to the second file into the currently open buffer cx.set_state(indoc! {"go to fiˇle2.rs"}, Mode::Normal); @@ -981,5 +984,21 @@ mod test { cx.workspace(|workspace, cx| { assert_active_item(workspace, "/root/dir/file2.rs", "This is file2.rs", cx); }); + + // Update editor to point to `file2.rs` + cx.editor = cx.workspace(|workspace, cx| workspace.active_item_as::(cx).unwrap()); + + // Put the path to the third file into the currently open buffer, + // but remove its suffix, because we want that lookup to happen automatically. + cx.set_state(indoc! {"go to fiˇle3"}, Mode::Normal); + + // Go to file3.rs + cx.simulate_keystrokes("g f"); + + // We now have three items + cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 3)); + cx.workspace(|workspace, cx| { + assert_active_item(workspace, "/root/dir/file3.rs", "go to file3", cx); + }); } }