1use agent_client_protocol as acp;
2use anyhow::{Result, bail};
3use std::path::PathBuf;
4
5#[derive(Clone, Debug, PartialEq, Eq)]
6pub enum MentionUri {
7 File(PathBuf),
8 Symbol(PathBuf, String),
9 Thread(acp::SessionId),
10 Rule(String),
11}
12
13impl MentionUri {
14 pub fn parse(input: &str) -> Result<Self> {
15 let url = url::Url::parse(input)?;
16 let path = url.path();
17 match url.scheme() {
18 "file" => {
19 if let Some(fragment) = url.fragment() {
20 Ok(Self::Symbol(path.into(), fragment.into()))
21 } else {
22 let file_path =
23 PathBuf::from(format!("{}{}", url.host_str().unwrap_or(""), path));
24
25 Ok(Self::File(file_path))
26 }
27 }
28 "zed" => {
29 if let Some(thread) = path.strip_prefix("/agent/thread/") {
30 Ok(Self::Thread(acp::SessionId(thread.into())))
31 } else if let Some(rule) = path.strip_prefix("/agent/rule/") {
32 Ok(Self::Rule(rule.into()))
33 } else {
34 bail!("invalid zed url: {:?}", input);
35 }
36 }
37 other => bail!("unrecognized scheme {:?}", other),
38 }
39 }
40
41 pub fn name(&self) -> String {
42 match self {
43 MentionUri::File(path) => path.file_name().unwrap().to_string_lossy().into_owned(),
44 MentionUri::Symbol(_path, name) => name.clone(),
45 MentionUri::Thread(thread) => thread.to_string(),
46 MentionUri::Rule(rule) => rule.clone(),
47 }
48 }
49
50 pub fn to_link(&self) -> String {
51 let name = self.name();
52 let uri = self.to_uri();
53 format!("[{name}]({uri})")
54 }
55
56 pub fn to_uri(&self) -> String {
57 match self {
58 MentionUri::File(path) => {
59 format!("file://{}", path.display())
60 }
61 MentionUri::Symbol(path, name) => {
62 format!("file://{}#{}", path.display(), name)
63 }
64 MentionUri::Thread(thread) => {
65 format!("zed:///agent/thread/{}", thread.0)
66 }
67 MentionUri::Rule(rule) => {
68 format!("zed:///agent/rule/{}", rule)
69 }
70 }
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn test_mention_uri_parse_and_display() {
80 // Test file URI
81 let file_uri = "file:///path/to/file.rs";
82 let parsed = MentionUri::parse(file_uri).unwrap();
83 match &parsed {
84 MentionUri::File(path) => assert_eq!(path.to_str().unwrap(), "/path/to/file.rs"),
85 _ => panic!("Expected File variant"),
86 }
87 assert_eq!(parsed.to_uri(), file_uri);
88
89 // Test symbol URI
90 let symbol_uri = "file:///path/to/file.rs#MySymbol";
91 let parsed = MentionUri::parse(symbol_uri).unwrap();
92 match &parsed {
93 MentionUri::Symbol(path, symbol) => {
94 assert_eq!(path.to_str().unwrap(), "/path/to/file.rs");
95 assert_eq!(symbol, "MySymbol");
96 }
97 _ => panic!("Expected Symbol variant"),
98 }
99 assert_eq!(parsed.to_uri(), symbol_uri);
100
101 // Test thread URI
102 let thread_uri = "zed:///agent/thread/session123";
103 let parsed = MentionUri::parse(thread_uri).unwrap();
104 match &parsed {
105 MentionUri::Thread(session_id) => assert_eq!(session_id.0.as_ref(), "session123"),
106 _ => panic!("Expected Thread variant"),
107 }
108 assert_eq!(parsed.to_uri(), thread_uri);
109
110 // Test rule URI
111 let rule_uri = "zed:///agent/rule/my_rule";
112 let parsed = MentionUri::parse(rule_uri).unwrap();
113 match &parsed {
114 MentionUri::Rule(rule) => assert_eq!(rule, "my_rule"),
115 _ => panic!("Expected Rule variant"),
116 }
117 assert_eq!(parsed.to_uri(), rule_uri);
118
119 // Test invalid scheme
120 assert!(MentionUri::parse("http://example.com").is_err());
121
122 // Test invalid zed path
123 assert!(MentionUri::parse("zed:///invalid/path").is_err());
124 }
125}