acp_thread: Decode file:// mention paths so non-ASCII names render correctly (#44983)

Daiki Takagi created

## Summary

This fixes a minor bug I found #44981 

- Fix percent-encoded filenames appearing in agent mentions after
message submission.
- Decode file:// paths in MentionUri::parse using the existing
urlencoding crate (already used elsewhere in the codebase).
- Add tests for non-ASCII file URIs.

## Screenshots

<img width="409" height="116" alt="image"
src="https://github.com/user-attachments/assets/32ef033b-6232-47c5-80c7-d5247d5dae88"
/>

Change summary

Cargo.lock                       |  1 +
crates/acp_thread/Cargo.toml     |  1 +
crates/acp_thread/src/mention.rs | 19 ++++++++++++++++++-
3 files changed, 20 insertions(+), 1 deletion(-)

Detailed changes

Cargo.lock 🔗

@@ -37,6 +37,7 @@ dependencies = [
  "terminal",
  "ui",
  "url",
+ "urlencoding",
  "util",
  "uuid",
  "watch",

crates/acp_thread/Cargo.toml 🔗

@@ -46,6 +46,7 @@ url.workspace = true
 util.workspace = true
 uuid.workspace = true
 watch.workspace = true
+urlencoding.workspace = true
 
 [dev-dependencies]
 env_logger.workspace = true

crates/acp_thread/src/mention.rs 🔗

@@ -4,12 +4,14 @@ use file_icons::FileIcons;
 use prompt_store::{PromptId, UserPromptId};
 use serde::{Deserialize, Serialize};
 use std::{
+    borrow::Cow,
     fmt,
     ops::RangeInclusive,
     path::{Path, PathBuf},
 };
 use ui::{App, IconName, SharedString};
 use url::Url;
+use urlencoding::decode;
 use util::paths::PathStyle;
 
 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
@@ -74,11 +76,13 @@ impl MentionUri {
         let path = url.path();
         match url.scheme() {
             "file" => {
-                let path = if path_style.is_windows() {
+                let normalized = if path_style.is_windows() {
                     path.trim_start_matches("/")
                 } else {
                     path
                 };
+                let decoded = decode(normalized).unwrap_or(Cow::Borrowed(normalized));
+                let path = decoded.as_ref();
 
                 if let Some(fragment) = url.fragment() {
                     let line_range = parse_line_range(fragment)?;
@@ -406,6 +410,19 @@ mod tests {
         assert_eq!(parsed.to_uri().to_string(), selection_uri);
     }
 
+    #[test]
+    fn test_parse_file_uri_with_non_ascii() {
+        let file_uri = uri!("file:///path/to/%E6%97%A5%E6%9C%AC%E8%AA%9E.txt");
+        let parsed = MentionUri::parse(file_uri, PathStyle::local()).unwrap();
+        match &parsed {
+            MentionUri::File { abs_path } => {
+                assert_eq!(abs_path, Path::new(path!("/path/to/日本語.txt")));
+            }
+            _ => panic!("Expected File variant"),
+        }
+        assert_eq!(parsed.to_uri().to_string(), file_uri);
+    }
+
     #[test]
     fn test_parse_untitled_selection_uri() {
         let selection_uri = uri!("zed:///agent/untitled-buffer#L1:10");